import from //branches/cupcake/...@142529
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..5053e7d
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1 @@
+include $(call all-subdir-makefiles)
diff --git a/PinyinIME/Android.mk b/PinyinIME/Android.mk
new file mode 100644
index 0000000..64224ae
--- /dev/null
+++ b/PinyinIME/Android.mk
@@ -0,0 +1,25 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+         $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := PinyinIME
+
+LOCAL_JNI_SHARED_LIBRARIES := libjni_pinyinime
+
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.inputmethod.pinyin.lib
+
+LOCAL_CERTIFICATE := shared
+
+# Make sure our dictionary file is not compressed, so we can read it with
+# a raw file descriptor.
+LOCAL_AAPT_FLAGS := -0 .dat
+
+include $(BUILD_PACKAGE)
+
+MY_PATH := $(LOCAL_PATH)
+
+include $(MY_PATH)/jni/Android.mk
+include $(MY_PATH)/lib/Android.mk
diff --git a/PinyinIME/AndroidManifest.xml b/PinyinIME/AndroidManifest.xml
new file mode 100644
index 0000000..e1ca30d
--- /dev/null
+++ b/PinyinIME/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.inputmethod.pinyin"
+    android:sharedUserId="android.uid.shared">
+        <uses-permission android:name="android.permission.VIBRATE"/>
+
+        <application android:icon="@drawable/app_icon"
+          android:label="@string/ime_name">
+            <service android:name=".PinyinDecoderService"
+                android:exported="true">
+                <intent-filter>
+                    <action android:name="com.android.inputmethod.pinyin.Decoder_Service" />
+                    <category android:name="android.intent.category.DEFAULT" />
+                </intent-filter>
+            </service>
+
+            <service android:name=".PinyinIME"
+                android:label="@string/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=".SettingsActivity"
+                android:label="@string/ime_settings_activity_name">
+                <intent-filter>
+                    <action android:name="android.intent.action.MAIN"/>
+                </intent-filter>
+            </activity>
+
+        </application>
+</manifest>
diff --git a/PinyinIME/jni/Android.mk b/PinyinIME/jni/Android.mk
new file mode 100644
index 0000000..141a0b7
--- /dev/null
+++ b/PinyinIME/jni/Android.mk
@@ -0,0 +1,32 @@
+LOCAL_PATH := $(call my-dir)
+
+### shared library
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	android/com_android_inputmethod_pinyin_PinyinDecoderService.cpp \
+	share/dictbuilder.cpp \
+	share/dictlist.cpp \
+	share/dicttrie.cpp \
+	share/lpicache.cpp \
+	share/matrixsearch.cpp \
+	share/mystdlib.cpp \
+	share/ngram.cpp \
+	share/pinyinime.cpp \
+	share/searchutility.cpp \
+	share/spellingtable.cpp \
+	share/spellingtrie.cpp \
+	share/splparser.cpp \
+	share/userdict.cpp \
+	share/utf16char.cpp \
+	share/utf16reader.cpp \
+	share/sync.cpp
+
+LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
+LOCAL_LDLIBS += -lpthread
+LOCAL_MODULE := libjni_pinyinime
+LOCAL_PRELINK_MODULE := false
+LOCAL_SHARED_LIBRARIES := libcutils libutils
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/PinyinIME/jni/android/com_android_inputmethod_pinyin_PinyinDecoderService.cpp b/PinyinIME/jni/android/com_android_inputmethod_pinyin_PinyinDecoderService.cpp
new file mode 100644
index 0000000..4362c30
--- /dev/null
+++ b/PinyinIME/jni/android/com_android_inputmethod_pinyin_PinyinDecoderService.cpp
@@ -0,0 +1,419 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include <cutils/log.h>
+#include <jni.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "../include/pinyinime.h"
+#include "../include/sync.h"
+#include "../include/userdict.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+using namespace ime_pinyin;
+
+#define RET_BUF_LEN 256
+
+static char16 retbuf[RET_BUF_LEN];
+static char16 (*predict_buf)[kMaxPredictSize + 1] = NULL;
+static size_t predict_len;
+
+static Sync sync_worker;
+
+static struct file_descriptor_offsets_t
+{
+  jclass mClass;
+  jfieldID mDescriptor;
+} gFileDescriptorOffsets;
+
+JNIEXPORT jboolean JNICALL nativeImOpenDecoder(JNIEnv* env, jclass jclazz,
+                                               jbyteArray fn_sys_dict,
+                                               jbyteArray fn_usr_dict) {
+  jbyte *fsd = (*env).GetByteArrayElements(fn_sys_dict, 0);
+  jbyte *fud = (*env).GetByteArrayElements(fn_usr_dict, 0);
+
+  if (im_open_decoder((const char*)fsd, (const char*)fud))
+    return JNI_TRUE;
+
+  return JNI_FALSE;
+}
+
+JNIEXPORT jboolean JNICALL nativeImOpenDecoderFd(JNIEnv* env, jclass jclazz,
+                                                 jobject fd_sys_dict,
+                                                 jlong startoffset,
+                                                 jlong length,
+                                                 jbyteArray fn_usr_dict) {
+  jint fd = env->GetIntField(fd_sys_dict, gFileDescriptorOffsets.mDescriptor);
+  jbyte *fud = (*env).GetByteArrayElements(fn_usr_dict, 0);
+
+  int newfd = dup(fd);
+  if (im_open_decoder_fd(newfd, startoffset, length, (const char*)fud)) {
+    close(newfd);
+    return JNI_TRUE;
+  }
+
+  close(newfd);
+  return JNI_FALSE;
+}
+
+JNIEXPORT void JNICALL nativeImSetMaxLens(JNIEnv* env, jclass jclazz,
+                                          jint max_sps_len,
+                                          jint max_hzs_len) {
+  im_set_max_lens(static_cast<size_t>(max_sps_len),
+                  static_cast<size_t>(max_hzs_len));
+  return;
+}
+
+JNIEXPORT jboolean JNICALL nativeImCloseDecoder(JNIEnv* env, jclass jclazz) {
+  im_close_decoder();
+  return JNI_TRUE;
+}
+
+JNIEXPORT jint JNICALL nativeImSearch(JNIEnv* env, jclass jclazz,
+                                      jbyteArray pybuf, jint pylen) {
+  jbyte *array_body = (*env).GetByteArrayElements(pybuf, 0);
+
+  if (NULL == array_body)
+    return 0;
+
+  return im_search((const char*)array_body, pylen);
+}
+
+JNIEXPORT jint JNICALL nativeImDelSearch(JNIEnv* env, jclass jclazz, jint pos,
+                                         jboolean is_pos_in_splid,
+                                         jboolean clear_fixed_this_step) {
+  return im_delsearch(pos, is_pos_in_splid, clear_fixed_this_step);
+}
+
+JNIEXPORT void JNICALL nativeImResetSearch(JNIEnv* env, jclass jclazz) {
+  im_reset_search();
+  return;
+}
+
+JNIEXPORT jint JNICALL nativeImAddLetter(JNIEnv *env, jclass clazz, jbyte ch) {
+  return im_add_letter(ch);
+}
+
+JNIEXPORT jstring JNICALL nativeImGetPyStr(JNIEnv* env, jclass jclazz,
+                                           jboolean decoded) {
+  size_t py_len;
+  const char *py = im_get_sps_str(&py_len);  // py_len gets decoded length
+  assert(NULL != py);
+  if (!decoded)
+    py_len = strlen(py);
+
+  const unsigned short *spl_start;
+  size_t len;
+  len = im_get_spl_start_pos(spl_start);
+
+  size_t i;
+  for (i = 0; i < py_len; i++)
+    retbuf[i] = py[i];
+  retbuf[i] = (char16)'\0';
+
+  jstring retstr = (*env).NewString((unsigned short*)retbuf, i);
+  return retstr;
+}
+
+JNIEXPORT jint JNICALL nativeImGetPyStrLen(JNIEnv* env, jclass jclazz,
+                                           jboolean decoded) {
+  size_t py_len;
+  const char *py = im_get_sps_str(&py_len);  // py_len gets decoded length
+  assert(NULL != py);
+  if (!decoded)
+    py_len = strlen(py);
+  return py_len;
+}
+
+JNIEXPORT jintArray JNICALL nativeImGetSplStart(JNIEnv* env, jclass jclazz) {
+  const unsigned short *spl_start;
+  size_t len;
+
+  // There will be len + 1 elements in the buffer when len > 0.
+  len = im_get_spl_start_pos(spl_start);
+
+  jintArray arr = (*env).NewIntArray(len + 2);
+  jint *arr_body = (*env).GetIntArrayElements(arr, 0);
+  assert(NULL != arr_body);
+  arr_body[0] = len; // element 0 is used to store the length of buffer.
+  for (size_t i = 0; i <= len; i++)
+    arr_body[i + 1] = spl_start[i];
+  return arr;
+}
+
+JNIEXPORT jstring JNICALL nativeImGetChoice(JNIEnv *env, jclass clazz,
+                                            jint candidateId) {
+  jstring retstr;
+  if(im_get_candidate(candidateId, retbuf, RET_BUF_LEN)) {
+    retstr = (*env).NewString(retbuf, utf16_strlen(retbuf));
+    return retstr;
+  } else {
+    retstr = (*env).NewString((unsigned short*)retbuf, 0);
+    return retstr;
+  }
+}
+
+JNIEXPORT jint JNICALL nativeImChoose(JNIEnv *env, jclass clazz,
+                                      jint choice_id) {
+  return im_choose(choice_id);
+}
+
+JNIEXPORT jint JNICALL nativeImCancelLastChoice(JNIEnv *env, jclass clazz) {
+  return im_cancel_last_choice();
+}
+
+JNIEXPORT jint JNICALL nativeImGetFixedLen(JNIEnv *env, jclass clazz) {
+  return im_get_fixed_len();
+}
+
+JNIEXPORT jboolean JNICALL nativeImCancelInput(JNIEnv *env, jclass clazz) {
+  if (im_cancel_input())
+    return JNI_TRUE;
+
+  return JNI_FALSE;
+}
+
+JNIEXPORT jboolean JNICALL nativeImFlushCache(JNIEnv *env, jclass clazz) {
+  im_flush_cache();
+  return JNI_TRUE;
+}
+
+JNIEXPORT jint JNICALL nativeImGetPredictsNum(JNIEnv *env, jclass clazz,
+                                              jstring fixed_str) {
+  char16 *fixed_ptr = (char16*)(*env).GetStringChars(fixed_str, false);
+  size_t fixed_len = (size_t)(*env).GetStringLength(fixed_str);
+
+  char16 fixed_buf[kMaxPredictSize + 1];
+
+  if (fixed_len > kMaxPredictSize) {
+    fixed_ptr += fixed_len - kMaxPredictSize;
+    fixed_len = kMaxPredictSize;
+  }
+  utf16_strncpy(fixed_buf, fixed_ptr, fixed_len);
+  fixed_buf[fixed_len] = (char16)'\0';
+
+  predict_len = im_get_predicts(fixed_buf, predict_buf);
+
+  return predict_len;
+}
+
+JNIEXPORT jstring JNICALL nativeImGetPredictItem(JNIEnv *env, jclass clazz,
+                                                 jint predict_no) {
+  jstring retstr;
+
+  if (predict_no < 0 || (size_t)predict_no >= predict_len) {
+    retstr = (*env).NewString((unsigned short*)predict_buf[0], 0);
+  } else {
+    retstr = (*env).NewString((unsigned short*)predict_buf[predict_no],
+                              utf16_strlen(predict_buf[predict_no]));
+  }
+  return retstr;
+}
+
+JNIEXPORT jboolean JNICALL nativeSyncBegin(JNIEnv *env, jclass clazz,
+                                           jbyteArray dict_file) {
+  jbyte *file_name = (*env).GetByteArrayElements(dict_file, 0);
+  if (true == sync_worker.begin((const char *)file_name))
+    return JNI_TRUE;
+  return JNI_FALSE;
+}
+
+JNIEXPORT jboolean JNICALL nativeSyncFinish(JNIEnv *env, jclass clazz) {
+  sync_worker.finish();
+  return JNI_TRUE;
+}
+
+JNIEXPORT jint JNICALL nativeSyncGetCapacity(JNIEnv *env, jclass clazz) {
+  return sync_worker.get_capacity();
+}
+
+JNIEXPORT jint JNICALL nativeSyncPutLemmas(JNIEnv *env, jclass clazz,
+                                           jstring tomerge) {
+
+  char16 *ptr = (char16*)(*env).GetStringChars(tomerge, NULL);
+  int len = (size_t)(*env).GetStringLength(tomerge);
+
+  int added = sync_worker.put_lemmas(ptr, len);
+
+  (*env).ReleaseStringChars(tomerge, ptr);
+
+  return added;
+}
+
+JNIEXPORT jstring JNICALL nativeSyncGetLemmas(JNIEnv *env, jclass clazz) {
+
+  int len = sync_worker.get_lemmas(retbuf, RET_BUF_LEN);
+  if (len == 0)
+    return NULL;
+  jstring retstr;
+  retstr = (*env).NewString((unsigned short*)retbuf, len);
+  return retstr;
+}
+
+JNIEXPORT jint JNICALL nativeSyncGetLastCount(JNIEnv *env, jclass clazz) {
+  return sync_worker.get_last_got_count();
+}
+
+JNIEXPORT jint JNICALL nativeSyncGetTotalCount(JNIEnv *env, jclass clazz) {
+  return sync_worker.get_total_count();
+}
+
+JNIEXPORT jboolean JNICALL nativeSyncClearLastGot(JNIEnv *env, jclass clazz) {
+  sync_worker.clear_last_got();
+  return JNI_TRUE;
+}
+
+/**
+ * Table of methods associated with a single class.
+ */
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    /* ------Functions for Pinyin-to-hanzi decoding begin--------->> */
+    { "nativeImOpenDecoder", "([B[B)Z",
+            (void*) nativeImOpenDecoder },
+    { "nativeImOpenDecoderFd", "(Ljava/io/FileDescriptor;JJ[B)Z",
+            (void*) nativeImOpenDecoderFd },
+    { "nativeImSetMaxLens", "(II)V",
+            (void*) nativeImSetMaxLens },
+    { "nativeImCloseDecoder", "()Z",
+            (void*) nativeImCloseDecoder },
+    { "nativeImSearch",  "([BI)I",
+            (void*) nativeImSearch },
+    { "nativeImDelSearch",  "(IZZ)I",
+            (void*) nativeImDelSearch },
+    { "nativeImResetSearch",  "()V",
+            (void*) nativeImResetSearch },
+    { "nativeImAddLetter", "(B)I",
+            (void*) nativeImAddLetter },
+    { "nativeImGetPyStr", "(Z)Ljava/lang/String;",
+            (void*) nativeImGetPyStr },
+    { "nativeImGetPyStrLen", "(Z)I",
+            (void*) nativeImGetPyStrLen },
+    { "nativeImGetSplStart", "()[I",
+            (void*) nativeImGetSplStart },
+    { "nativeImGetChoice", "(I)Ljava/lang/String;",
+            (void*) nativeImGetChoice },
+    { "nativeImChoose", "(I)I",
+            (void*) nativeImChoose },
+    { "nativeImCancelLastChoice", "()I",
+            (void*) nativeImCancelLastChoice },
+    { "nativeImGetFixedLen", "()I",
+            (void*) nativeImGetFixedLen },
+    { "nativeImGetPredictsNum", "(Ljava/lang/String;)I",
+            (void*) nativeImGetPredictsNum },
+    { "nativeImGetPredictItem", "(I)Ljava/lang/String;",
+            (void*) nativeImGetPredictItem },
+    { "nativeImCancelInput", "()Z",
+            (void*) nativeImCancelInput },
+    { "nativeImFlushCache", "()Z",
+            (void*) nativeImFlushCache },
+    /* <<----Functions for Pinyin-to-hanzi decoding end------------- */
+
+    /* ------Functions for sync begin----------------------------->> */
+    { "nativeSyncBegin", "([B)Z",
+            (void*) nativeSyncBegin },
+    { "nativeSyncFinish", "()Z",
+            (void*) nativeSyncFinish },
+    { "nativeSyncPutLemmas", "(Ljava/lang/String;)I",
+            (void*) nativeSyncPutLemmas },
+    { "nativeSyncGetLemmas", "()Ljava/lang/String;",
+            (void*) nativeSyncGetLemmas },
+    { "nativeSyncGetLastCount", "()I",
+            (void*) nativeSyncGetLastCount },
+    { "nativeSyncGetTotalCount", "()I",
+            (void*) nativeSyncGetTotalCount },
+    { "nativeSyncClearLastGot", "()Z",
+            (void*) nativeSyncClearLastGot },
+    { "nativeSyncGetCapacity", "()I",
+            (void*) nativeSyncGetCapacity },
+    /* <<----Functions for sync end--------------------------------- */
+};
+
+
+/*
+ * Register several native methods for one class.
+ */
+static int registerNativeMethods(JNIEnv* env, const char* className,
+    JNINativeMethod* gMethods, int numMethods)
+{
+    jclass clazz;
+
+    clazz = (*env).FindClass(className);
+    if (clazz == NULL) {
+        return JNI_FALSE;
+    }
+    if ((*env).RegisterNatives(clazz, gMethods, numMethods) < 0) {
+        return JNI_FALSE;
+    }
+
+    clazz = env->FindClass("java/io/FileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find Java class java.io.FileDescriptor");
+    gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I");
+    LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL,
+                 "Unable to find descriptor field in java.io.FileDescriptor");
+
+    return JNI_TRUE;
+}
+
+/*
+ * Register native methods for all classes we know about.
+ */
+static int registerNatives(JNIEnv* env)
+{
+    if (!registerNativeMethods(env,
+           "com/android/inputmethod/pinyin/PinyinDecoderService",
+            gMethods, sizeof(gMethods) / sizeof(gMethods[0])))
+        return JNI_FALSE;
+
+    return JNI_TRUE;
+}
+
+/*
+ * Set some test stuff up.
+ *
+ * Returns the JNI version on success, -1 on failure.
+ */
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+    JNIEnv* env = NULL;
+    jint result = -1;
+
+    if ((*vm).GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+        goto bail;
+    }
+    assert(env != NULL);
+
+    if (!registerNatives(env)) {
+        goto bail;
+    }
+
+    /* success -- return valid version number */
+    result = JNI_VERSION_1_4;
+
+bail:
+    return result;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/PinyinIME/jni/command/Makefile b/PinyinIME/jni/command/Makefile
new file mode 100644
index 0000000..0722932
--- /dev/null
+++ b/PinyinIME/jni/command/Makefile
@@ -0,0 +1,33 @@
+CC=gcc
+CFLAGS= -g -Wall -std=c99
+CPP=g++
+CPPFLAGS= -g3 -Wall -lpthread
+
+PINYINIME_DICTBUILDER=pinyinime_dictbuilder
+
+LIBRARY_SRC= \
+	    ../share/dictbuilder.cpp \
+	    ../share/dictlist.cpp \
+	    ../share/dicttrie.cpp \
+	    ../share/lpicache.cpp \
+	    ../share/mystdlib.cpp \
+	    ../share/ngram.cpp \
+	    ../share/searchutility.cpp \
+	    ../share/spellingtable.cpp \
+	    ../share/spellingtrie.cpp \
+	    ../share/splparser.cpp \
+	    ../share/utf16char.cpp \
+	    ../share/utf16reader.cpp \
+
+all: engine
+
+engine: $(PINYINIME_DICTBUILDER)
+
+$(PINYINIME_DICTBUILDER): $(LIBRARY_SRC) pinyinime_dictbuilder.cpp
+	@$(CPP) $(CPPFLAGS) -o $@ $?
+
+
+clean:
+	-rm -rf $(PINYINIME_DICTBUILDER)
+
+.PHONY: clean
diff --git a/PinyinIME/jni/command/pinyinime_dictbuilder.cpp b/PinyinIME/jni/command/pinyinime_dictbuilder.cpp
new file mode 100644
index 0000000..b7f9606
--- /dev/null
+++ b/PinyinIME/jni/command/pinyinime_dictbuilder.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include "../include/dicttrie.h"
+
+using namespace ime_pinyin;
+
+/**
+ * Build binary dictionary model. Make sure that ___BUILD_MODEL___ is defined
+ * in dictdef.h.
+ */
+int main(int argc, char* argv[]) {
+  DictTrie* dict_trie = new DictTrie();
+  bool success;
+  if (argc >= 3)
+     success = dict_trie->build_dict(argv[1], argv[2]);
+  else
+     success = dict_trie->build_dict("../data/rawdict_utf16_65105_freq.txt",
+                                     "../data/valid_utf16.txt");
+
+  if (success) {
+    printf("Build dictionary successfully.\n");
+  } else {
+    printf("Build dictionary unsuccessfully.\n");
+    return -1;
+  }
+
+  success = dict_trie->save_dict("../../res/raw/dict_pinyin.dat");
+
+  if (success) {
+    printf("Save dictionary successfully.\n");
+  } else {
+    printf("Save dictionary unsuccessfully.\n");
+    return -1;
+  }
+
+  return 0;
+}
diff --git a/PinyinIME/jni/data/rawdict_utf16_65105_freq.txt b/PinyinIME/jni/data/rawdict_utf16_65105_freq.txt
new file mode 100644
index 0000000..28805ba
--- /dev/null
+++ b/PinyinIME/jni/data/rawdict_utf16_65105_freq.txt
Binary files differ
diff --git a/PinyinIME/jni/data/valid_utf16.txt b/PinyinIME/jni/data/valid_utf16.txt
new file mode 100644
index 0000000..fecc67e
--- /dev/null
+++ b/PinyinIME/jni/data/valid_utf16.txt
Binary files differ
diff --git a/PinyinIME/jni/include/atomdictbase.h b/PinyinIME/jni/include/atomdictbase.h
new file mode 100644
index 0000000..0a70a51
--- /dev/null
+++ b/PinyinIME/jni/include/atomdictbase.h
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+
+/**
+ * This class defines AtomDictBase class which is the base class for all atom
+ * dictionaries. Atom dictionaries are managed by the decoder class
+ * MatrixSearch.
+ *
+ * When the user appends a new character to the Pinyin string, all enabled atom
+ * dictionaries' extend_dict() will be called at least once to get candidates
+ * ended in this step (the information of starting step is also given in the
+ * parameter). Usually, when extend_dict() is called, a MileStoneHandle object
+ * returned by a previous calling for a earlier step is given to speed up the
+ * look-up process, and a new MileStoneHandle object will be returned if
+ * the extension is successful.
+ *
+ * A returned MileStoneHandle object should keep alive until Function
+ * reset_milestones() is called and this object is noticed to be reset.
+ *
+ * Usually, the atom dictionary can use step information to manage its
+ * MileStoneHandle objects, or it can make the objects in ascendant order to
+ * make the reset easier.
+ *
+ * When the decoder loads the dictionary, it will give a starting lemma id for
+ * this atom dictionary to map a inner id to a global id. Global ids should be
+ * used when an atom dictionary talks to any component outside.
+ */
+#ifndef PINYINIME_INCLUDE_ATOMDICTBASE_H__
+#define PINYINIME_INCLUDE_ATOMDICTBASE_H__
+
+#include <stdlib.h>
+#include "./dictdef.h"
+#include "./searchutility.h"
+
+namespace ime_pinyin {
+class AtomDictBase {
+ public:
+  virtual ~AtomDictBase() {}
+
+  /**
+   * Load an atom dictionary from a file.
+   *
+   * @param file_name The file name to load dictionary.
+   * @param start_id The starting id used for this atom dictionary.
+   * @param end_id The end id (included) which can be used for this atom
+   * dictionary. User dictionary will always use the last id space, so it can
+   * ignore this paramter. All other atom dictionaries should check this
+   * parameter.
+   * @return True if succeed.
+   */
+  virtual bool load_dict(const char *file_name, LemmaIdType start_id,
+                         LemmaIdType end_id) = 0;
+
+  /**
+   * Close this atom dictionary.
+   *
+   * @return True if succeed.
+   */
+  virtual bool close_dict() = 0;
+
+  /**
+   * Get the total number of lemmas in this atom dictionary.
+   *
+   * @return The total number of lemmas.
+   */
+  virtual size_t number_of_lemmas() = 0;
+
+  /**
+   * This function is called by the decoder when user deletes a character from
+   * the input string, or begins a new input string.
+   *
+   * Different atom dictionaries may implement this function in different way.
+   * an atom dictionary can use one of these two parameters (or both) to reset
+   * its corresponding MileStoneHandle objects according its detailed
+   * implementation.
+   *
+   * For example, if an atom dictionary uses step information to manage its
+   * MileStoneHandle objects, parameter from_step can be used to identify which
+   * objects should be reset; otherwise, if another atom dictionary does not
+   * use the detailed step information, it only uses ascendant handles
+   * (according to step. For the same step, earlier call, smaller handle), it
+   * can easily reset those MileStoneHandle which are larger than from_handle.
+   *
+   * The decoder always reset the decoding state by step. So when it begins
+   * resetting, it will call reset_milestones() of its atom dictionaries with
+   * the step information, and the MileStoneHandle objects returned by the
+   * earliest calling of extend_dict() for that step.
+   *
+   * If an atom dictionary does not implement incremental search, this function
+   * can be totally ignored.
+   *
+   * @param from_step From which step(included) the MileStoneHandle
+   * objects should be reset.
+   * @param from_handle The ealiest MileStoneHandle object for step from_step
+   */
+  virtual void reset_milestones(uint16 from_step,
+                                MileStoneHandle from_handle) = 0;
+
+  /**
+   * Used to extend in this dictionary. The handle returned should keep valid
+   * until reset_milestones() is called.
+   *
+   * @param from_handle Its previous returned extended handle without the new
+   * spelling id, it can be used to speed up the extending.
+   * @param dep The paramter used for extending.
+   * @param lpi_items Used to fill in the lemmas matched.
+   * @param lpi_max The length of the buffer
+   * @param lpi_num Used to return the newly added items.
+   * @return The new mile stone for this extending. 0 if fail.
+   */
+  virtual MileStoneHandle extend_dict(MileStoneHandle from_handle,
+                                      const DictExtPara *dep,
+                                      LmaPsbItem *lpi_items,
+                                      size_t lpi_max, size_t *lpi_num) = 0;
+
+  /**
+   * Get lemma items with scores according to a spelling id stream.
+   * This atom dictionary does not need to sort the returned items.
+   *
+   * @param splid_str The spelling id stream buffer.
+   * @param splid_str_len The length of the spelling id stream buffer.
+   * @param lpi_items Used to return matched lemma items with scores.
+   * @param lpi_max The maximum size of the buffer to return result.
+   * @return The number of matched items which have been filled in to lpi_items.
+   */
+  virtual size_t get_lpis(const uint16 *splid_str, uint16 splid_str_len,
+                          LmaPsbItem *lpi_items, size_t lpi_max) = 0;
+
+  /**
+   * Get a lemma string (The Chinese string) by the given lemma id.
+   *
+   * @param id_lemma The lemma id to get the string.
+   * @param str_buf The buffer to return the Chinese string.
+   * @param str_max The maximum size of the buffer.
+   * @return The length of the string, 0 if fail.
+   */
+  virtual uint16 get_lemma_str(LemmaIdType id_lemma, char16 *str_buf,
+                               uint16 str_max) = 0;
+
+  /**
+   * Get the full spelling ids for the given lemma id.
+   * If the given buffer is too short, return 0.
+   *
+   * @param splids Used to return the spelling ids.
+   * @param splids_max The maximum buffer length of splids.
+   * @param arg_valid Used to indicate if the incoming parameters have been
+   * initialized are valid. If it is true, the splids and splids_max are valid
+   * and there may be half ids in splids to be updated to full ids. In this
+   * case, splids_max is the number of valid ids in splids.
+   * @return The number of ids in the buffer.
+   */
+  virtual uint16 get_lemma_splids(LemmaIdType id_lemma, uint16 *splids,
+                                  uint16 splids_max, bool arg_valid) = 0;
+
+  /**
+   * Function used for prediction.
+   * No need to sort the newly added items.
+   *
+   * @param last_hzs The last n Chinese chracters(called Hanzi), its length
+   * should be less than or equal to kMaxPredictSize.
+   * @param hzs_len specifies the length(<= kMaxPredictSize) of the history.
+   * @param npre_items Used used to return the result.
+   * @param npre_max The length of the buffer to return result
+   * @param b4_used Number of prediction result (from npre_items[-b4_used])
+   * from other atom dictionaries. A atom ditionary can just ignore it.
+   * @return The number of prediction result from this atom dictionary.
+   */
+  virtual size_t predict(const char16 last_hzs[], uint16 hzs_len,
+                         NPredictItem *npre_items, size_t npre_max,
+                         size_t b4_used) = 0;
+
+  /**
+   * Add a lemma to the dictionary. If the dictionary allows to add new
+   * items and this item does not exist, add it.
+   *
+   * @param lemma_str The Chinese string of the lemma.
+   * @param splids The spelling ids of the lemma.
+   * @param lemma_len The length of the Chinese lemma.
+   * @param count The frequency count for this lemma.
+   */
+  virtual LemmaIdType put_lemma(char16 lemma_str[], uint16 splids[],
+                                uint16 lemma_len, uint16 count) = 0;
+
+  /**
+   * Update a lemma's occuring count.
+   *
+   * @param lemma_id The lemma id to update.
+   * @param delta_count The frequnecy count to ajust.
+   * @param selected Indicate whether this lemma is selected by user and
+   * submitted to target edit box.
+   * @return The id if succeed, 0 if fail.
+   */
+  virtual LemmaIdType update_lemma(LemmaIdType lemma_id, int16 delta_count,
+                                   bool selected) = 0;
+
+  /**
+   * Get the lemma id for the given lemma.
+   *
+   * @param lemma_str The Chinese string of the lemma.
+   * @param splids The spelling ids of the lemma.
+   * @param lemma_len The length of the lemma.
+   * @return The matched lemma id, or 0 if fail.
+   */
+  virtual LemmaIdType get_lemma_id(char16 lemma_str[], uint16 splids[],
+                                   uint16 lemma_len) = 0;
+
+  /**
+   * Get the lemma score.
+   *
+   * @param lemma_id The lemma id to get score.
+   * @return The score of the lemma, or 0 if fail.
+   */
+  virtual LmaScoreType get_lemma_score(LemmaIdType lemma_id) = 0;
+
+  /**
+   * Get the lemma score.
+   *
+   * @param lemma_str The Chinese string of the lemma.
+   * @param splids The spelling ids of the lemma.
+   * @param lemma_len The length of the lemma.
+   * @return The score of the lamm, or 0 if fail.
+   */
+  virtual LmaScoreType get_lemma_score(char16 lemma_str[], uint16 splids[],
+                                uint16 lemma_len) = 0;
+
+  /**
+   * If the dictionary allowed, remove a lemma from it.
+   *
+   * @param lemma_id The id of the lemma to remove.
+   * @return True if succeed.
+   */
+  virtual bool remove_lemma(LemmaIdType lemma_id) = 0;
+
+  /**
+   * Get the total occuring count of this atom dictionary.
+   *
+   * @return The total occuring count of this atom dictionary.
+   */
+  virtual size_t get_total_lemma_count() = 0;
+
+  /**
+   * Set the total occuring count of other atom dictionaries.
+   *
+   * @param count The total occuring count of other atom dictionaies.
+   */
+  virtual void set_total_lemma_count_of_others(size_t count) = 0;
+
+  /**
+   * Notify this atom dictionary to flush the cached data to persistent storage
+   * if necessary.
+   */
+  virtual void flush_cache() = 0;
+};
+}
+
+#endif  // PINYINIME_INCLUDE_ATOMDICTBASE_H__
diff --git a/PinyinIME/jni/include/dictbuilder.h b/PinyinIME/jni/include/dictbuilder.h
new file mode 100644
index 0000000..da0d6cd
--- /dev/null
+++ b/PinyinIME/jni/include/dictbuilder.h
@@ -0,0 +1,171 @@
+/*
+ * 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 PINYINIME_INCLUDE_DICTBUILDER_H__
+#define PINYINIME_INCLUDE_DICTBUILDER_H__
+
+#include <stdlib.h>
+#include "./utf16char.h"
+#include "./dictdef.h"
+#include "./dictlist.h"
+#include "./spellingtable.h"
+#include "./spellingtrie.h"
+#include "./splparser.h"
+
+namespace ime_pinyin {
+
+#ifdef ___BUILD_MODEL___
+
+#define ___DO_STATISTICS___
+
+class DictTrie;
+
+class DictBuilder {
+ private:
+  // The raw lemma array buffer.
+  LemmaEntry *lemma_arr_;
+  size_t lemma_num_;
+
+  // Used to store all possible single char items.
+  // Two items may have the same Hanzi while their spelling ids are different.
+  SingleCharItem *scis_;
+  size_t scis_num_;
+
+  // In the tree, root's level is -1.
+  // Lemma nodes for root, and level 0
+  LmaNodeLE0 *lma_nodes_le0_;
+
+  // Lemma nodes for layers whose levels are deeper than 0
+  LmaNodeGE1 *lma_nodes_ge1_;
+
+  // Number of used lemma nodes
+  size_t lma_nds_used_num_le0_;
+  size_t lma_nds_used_num_ge1_;
+
+  // Used to store homophonies' ids.
+  LemmaIdType *homo_idx_buf_;
+  // Number of homophonies each of which only contains one Chinese character.
+  size_t homo_idx_num_eq1_;
+  // Number of homophonies each of which contains more than one character.
+  size_t homo_idx_num_gt1_;
+
+  // The items with highest scores.
+  LemmaEntry *top_lmas_;
+  size_t top_lmas_num_;
+
+  SpellingTable *spl_table_;
+  SpellingParser *spl_parser_;
+
+#ifdef ___DO_STATISTICS___
+  size_t max_sonbuf_len_[kMaxLemmaSize];
+  size_t max_homobuf_len_[kMaxLemmaSize];
+
+  size_t total_son_num_[kMaxLemmaSize];
+  size_t total_node_hasson_[kMaxLemmaSize];
+  size_t total_sonbuf_num_[kMaxLemmaSize];
+  size_t total_sonbuf_allnoson_[kMaxLemmaSize];
+  size_t total_node_in_sonbuf_allnoson_[kMaxLemmaSize];
+  size_t total_homo_num_[kMaxLemmaSize];
+
+  size_t sonbufs_num1_;     // Number of son buffer with only 1 son
+  size_t sonbufs_numgt1_;   // Number of son buffer with more 1 son;
+
+  size_t total_lma_node_num_;
+
+  void stat_init();
+  void stat_print();
+#endif
+
+ public:
+
+  DictBuilder();
+  ~DictBuilder();
+
+  // Build dictionary trie from the file fn_raw. File fn_validhzs provides
+  // valid chars. If fn_validhzs is NULL, only chars in GB2312 will be
+  // included.
+  bool build_dict(const char* fn_raw, const char* fn_validhzs,
+                  DictTrie *dict_trie);
+
+ private:
+  // Fill in the buffer with id. The caller guarantees that the paramters are
+  // vaild.
+  void id_to_charbuf(unsigned char *buf, LemmaIdType id);
+
+  // Update the offset of sons for a node.
+  void set_son_offset(LmaNodeGE1 *node, size_t offset);
+
+  // Update the offset of homophonies' ids for a node.
+  void set_homo_id_buf_offset(LmaNodeGE1 *node, size_t offset);
+
+  // Format a speling string.
+  void format_spelling_str(char *spl_str);
+
+  // Sort the lemma_arr by the hanzi string, and give each of unique items
+  // a id. Why we need to sort the lemma list according to their Hanzi string
+  // is to find items started by a given prefix string to do prediction.
+  // Actually, the single char items are be in other order, for example,
+  // in spelling id order, etc.
+  // Return value is next un-allocated idx available.
+  LemmaIdType sort_lemmas_by_hz();
+
+  // Build the SingleCharItem list, and fill the hanzi_scis_ids in the
+  // lemma buffer lemma_arr_.
+  // This function should be called after the lemma array is ready.
+  // Return the number of unique SingleCharItem elements.
+  size_t build_scis();
+
+  // Construct a subtree using a subset of the spelling array (from
+  // item_star to item_end)
+  // parent is the parent node to update the necessary information
+  // parent can be a member of LmaNodeLE0 or LmaNodeGE1
+  bool construct_subset(void* parent, LemmaEntry* lemma_arr,
+                        size_t item_start, size_t item_end, size_t level);
+
+
+  // Read valid Chinese Hanzis from the given file.
+  // num is used to return number of chars.
+  // The return buffer is sorted and caller needs to free the returned buffer.
+  char16* read_valid_hanzis(const char *fn_validhzs, size_t *num);
+
+
+  // Read a raw dictionary. max_item is the maximum number of items. If there
+  // are more items in the ditionary, only the first max_item will be read.
+  // Returned value is the number of items successfully read from the file.
+  size_t read_raw_dict(const char* fn_raw, const char *fn_validhzs,
+                       size_t max_item);
+
+  // Try to find if a character is in hzs buffer.
+  bool hz_in_hanzis_list(const char16 *hzs, size_t hzs_len, char16 hz);
+
+  // Try to find if all characters in str are in hzs buffer.
+  bool str_in_hanzis_list(const char16 *hzs, size_t hzs_len,
+                          const char16 *str, size_t str_len);
+
+  // Get these lemmas with toppest scores.
+  void get_top_lemmas();
+
+  // Allocate resource to build dictionary.
+  // lma_num is the number of items to be loaded
+  bool alloc_resource(size_t lma_num);
+
+  // Free resource.
+  void free_resource();
+};
+#endif  // ___BUILD_MODEL___
+}
+
+#endif  // PINYINIME_INCLUDE_DICTBUILDER_H__
diff --git a/PinyinIME/jni/include/dictdef.h b/PinyinIME/jni/include/dictdef.h
new file mode 100644
index 0000000..3e79d98
--- /dev/null
+++ b/PinyinIME/jni/include/dictdef.h
@@ -0,0 +1,157 @@
+/*
+ * 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 PINYINIME_INCLUDE_DICTDEF_H__
+#define PINYINIME_INCLUDE_DICTDEF_H__
+
+#include <stdlib.h>
+#include "./utf16char.h"
+
+namespace ime_pinyin {
+
+// Enable the following line when building the binary dictionary model.
+// #define ___BUILD_MODEL___
+
+typedef unsigned char      uint8;
+typedef unsigned short     uint16;
+typedef unsigned int       uint32;
+
+typedef signed char        int8;
+typedef short              int16;
+typedef int                int32;
+typedef long long          int64;
+typedef unsigned long long uint64;
+
+const bool kPrintDebug0 = false;
+const bool kPrintDebug1 = false;
+const bool kPrintDebug2 = false;
+
+// The max length of a lemma.
+const size_t kMaxLemmaSize = 8;
+
+// The max length of a Pinyin (spelling).
+const size_t kMaxPinyinSize = 6;
+
+// The number of half spelling ids. For Chinese Pinyin, there 30 half ids.
+// See SpellingTrie.h for details.
+const size_t kHalfSpellingIdNum = 29;
+
+// The maximum number of full spellings. For Chinese Pinyin, there are only
+// about 410 spellings.
+// If change this value is bigger(needs more bits), please also update
+// other structures like SpellingNode, to make sure than a spelling id can be
+// stored.
+// -1 is because that 0 is never used.
+const size_t kMaxSpellingNum = 512 - kHalfSpellingIdNum - 1;
+const size_t kMaxSearchSteps = 40;
+
+// One character predicts its following characters.
+const size_t kMaxPredictSize = (kMaxLemmaSize - 1);
+
+// LemmaIdType must always be size_t.
+typedef size_t LemmaIdType;
+const size_t kLemmaIdSize = 3;  // Actually, a Id occupies 3 bytes in storage.
+const size_t kLemmaIdComposing = 0xffffff;
+
+typedef uint16 LmaScoreType;
+typedef uint16 KeyScoreType;
+
+// Number of items with highest score are kept for prediction purpose.
+const size_t kTopScoreLemmaNum = 10;
+
+const size_t kMaxPredictNumByGt3 = 1;
+const size_t kMaxPredictNumBy3 = 2;
+const size_t kMaxPredictNumBy2 = 2;
+
+// The last lemma id (included) for the system dictionary. The system
+// dictionary's ids always start from 1.
+const LemmaIdType kSysDictIdEnd = 500000;
+
+// The first lemma id for the user dictionary.
+const LemmaIdType kUserDictIdStart = 500001;
+
+// The last lemma id (included) for the user dictionary.
+const LemmaIdType kUserDictIdEnd = 600000;
+
+typedef struct {
+  uint16 half_splid:5;
+  uint16 full_splid:11;
+} SpellingId, *PSpellingId;
+
+
+/**
+ * We use different node types for different layers
+ * Statistical data of the building result for a testing dictionary:
+ *                              root,   level 0,   level 1,   level 2,   level 3
+ * max son num of one node:     406        280         41          2          -
+ * max homo num of one node:      0         90         23          2          2
+ * total node num of a layer:     1        406      31766      13516        993
+ * total homo num of a layer:     9       5674      44609      12667        995
+ *
+ * The node number for root and level 0 won't be larger than 500
+ * According to the information above, two kinds of nodes can be used; one for
+ * root and level 0, the other for these layers deeper than 0.
+ *
+ * LE = less and equal,
+ * A node occupies 16 bytes. so, totallly less than 16 * 500 = 8K
+ */
+struct LmaNodeLE0 {
+  size_t son_1st_off;
+  size_t homo_idx_buf_off;
+  uint16 spl_idx;
+  uint16 num_of_son;
+  uint16 num_of_homo;
+};
+
+/**
+ * GE = great and equal
+ * A node occupies 8 bytes.
+ */
+struct LmaNodeGE1 {
+  uint16 son_1st_off_l;        // Low bits of the son_1st_off
+  uint16 homo_idx_buf_off_l;   // Low bits of the homo_idx_buf_off_1
+  uint16 spl_idx;
+  unsigned char num_of_son;            // number of son nodes
+  unsigned char num_of_homo;           // number of homo words
+  unsigned char son_1st_off_h;         // high bits of the son_1st_off
+  unsigned char homo_idx_buf_off_h;    // high bits of the homo_idx_buf_off
+};
+
+#ifdef ___BUILD_MODEL___
+struct SingleCharItem {
+  float freq;
+  char16 hz;
+  SpellingId splid;
+};
+
+struct LemmaEntry {
+  LemmaIdType idx_by_py;
+  LemmaIdType idx_by_hz;
+  char16 hanzi_str[kMaxLemmaSize + 1];
+
+  // The SingleCharItem id for each Hanzi.
+  uint16 hanzi_scis_ids[kMaxLemmaSize];
+
+  uint16 spl_idx_arr[kMaxLemmaSize + 1];
+  char pinyin_str[kMaxLemmaSize][kMaxPinyinSize + 1];
+  unsigned char hz_str_len;
+  float freq;
+};
+#endif  // ___BUILD_MODEL___
+
+}  //  namespace ime_pinyin
+
+#endif  // PINYINIME_INCLUDE_DICTDEF_H__
diff --git a/PinyinIME/jni/include/dictlist.h b/PinyinIME/jni/include/dictlist.h
new file mode 100644
index 0000000..5fcc12f
--- /dev/null
+++ b/PinyinIME/jni/include/dictlist.h
@@ -0,0 +1,120 @@
+/*
+ * 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 PINYINIME_INCLUDE_DICTLIST_H__
+#define PINYINIME_INCLUDE_DICTLIST_H__
+
+#include <stdlib.h>
+#include <stdio.h>
+#include "./dictdef.h"
+#include "./searchutility.h"
+#include "./spellingtrie.h"
+#include "./utf16char.h"
+
+namespace ime_pinyin {
+
+class DictList {
+ private:
+  bool initialized_;
+
+  const SpellingTrie *spl_trie_;
+
+  // Number of SingCharItem. The first is blank, because id 0 is invalid.
+  size_t scis_num_;
+  char16 *scis_hz_;
+  SpellingId *scis_splid_;
+
+  // The large memory block to store the word list.
+  char16 *buf_;
+
+  // Starting position of those words whose lengths are i+1, counted in
+  // char16
+  size_t start_pos_[kMaxLemmaSize + 1];
+
+  size_t start_id_[kMaxLemmaSize + 1];
+
+  int (*cmp_func_[kMaxLemmaSize])(const void *, const void *);
+
+  bool alloc_resource(size_t buf_size, size_t scim_num);
+
+  void free_resource();
+
+#ifdef ___BUILD_MODEL___
+  // Calculate the requsted memory, including the start_pos[] buffer.
+  size_t calculate_size(const LemmaEntry *lemma_arr, size_t lemma_num);
+
+  void fill_scis(const SingleCharItem *scis, size_t scis_num);
+
+  // Copy the related content to the inner buffer
+  // It should be called after calculate_size()
+  void fill_list(const LemmaEntry *lemma_arr, size_t lemma_num);
+
+  // Find the starting position for the buffer of those 2-character Chinese word
+  // whose first character is the given Chinese character.
+  char16* find_pos2_startedbyhz(char16 hz_char);
+#endif
+
+  // Find the starting position for the buffer of those words whose lengths are
+  // word_len. The given parameter cmp_func decides how many characters from
+  // beginning will be used to compare.
+  char16* find_pos_startedbyhzs(const char16 last_hzs[],
+                                size_t word_Len,
+                                int (*cmp_func)(const void *, const void *));
+
+ public:
+
+  DictList();
+  ~DictList();
+
+  bool save_list(FILE *fp);
+  bool load_list(FILE *fp);
+
+#ifdef ___BUILD_MODEL___
+  // Init the list from the LemmaEntry array.
+  // lemma_arr should have been sorted by the hanzi_str, and have been given
+  // ids from 1
+  bool init_list(const SingleCharItem *scis, size_t scis_num,
+                 const LemmaEntry *lemma_arr, size_t lemma_num);
+#endif
+
+  // Get the hanzi string for the given id
+  uint16 get_lemma_str(LemmaIdType id_hz, char16 *str_buf, uint16 str_max);
+
+  void convert_to_hanzis(char16 *str, uint16 str_len);
+
+  void convert_to_scis_ids(char16 *str, uint16 str_len);
+
+  // last_hzs stores the last n Chinese characters history, its length should be
+  // less or equal than kMaxPredictSize.
+  // hzs_len specifies the length(<= kMaxPredictSize).
+  // predict_buf is used to store the result.
+  // buf_len specifies the buffer length.
+  // b4_used specifies how many items before predict_buf have been used.
+  // Returned value is the number of newly added items.
+  size_t predict(const char16 last_hzs[], uint16 hzs_len,
+                 NPredictItem *npre_items, size_t npre_max,
+                 size_t b4_used);
+
+  // If half_splid is a valid half spelling id, return those full spelling
+  // ids which share this half id.
+  uint16 get_splids_for_hanzi(char16 hanzi, uint16 half_splid,
+                              uint16 *splids, uint16 max_splids);
+
+  LemmaIdType get_lemma_id(const char16 *str, uint16 str_len);
+};
+}
+
+#endif  // PINYINIME_INCLUDE_DICTLIST_H__
diff --git a/PinyinIME/jni/include/dicttrie.h b/PinyinIME/jni/include/dicttrie.h
new file mode 100644
index 0000000..268624f
--- /dev/null
+++ b/PinyinIME/jni/include/dicttrie.h
@@ -0,0 +1,233 @@
+/*
+ * 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 PINYINIME_INCLUDE_DICTTRIE_H__
+#define PINYINIME_INCLUDE_DICTTRIE_H__
+
+#include <stdlib.h>
+#include "./atomdictbase.h"
+#include "./dictdef.h"
+#include "./dictlist.h"
+#include "./searchutility.h"
+
+namespace ime_pinyin {
+
+class DictTrie : AtomDictBase {
+ private:
+  typedef struct ParsingMark {
+    size_t node_offset:24;
+    size_t node_num:8;           // Number of nodes with this spelling id given
+                                 // by spl_id. If spl_id is a Shengmu, for nodes
+                                 // in the first layer of DictTrie, it equals to
+                                 // SpellingTrie::shm2full_num(); but for those
+                                 // nodes which are not in the first layer,
+                                 // node_num < SpellingTrie::shm2full_num().
+                                 // For a full spelling id, node_num = 1;
+  };
+
+  // Used to indicate an extended mile stone.
+  // An extended mile stone is used to mark a partial match in the dictionary
+  // trie to speed up further potential extending.
+  // For example, when the user inputs "w", a mile stone is created to mark the
+  // partial match status, so that when user inputs another char 'm', it will be
+  // faster to extend search space based on this mile stone.
+  //
+  // For partial match status of "wm", there can be more than one sub mile
+  // stone, for example, "wm" can be matched to "wanm", "wom", ..., etc, so
+  // there may be more one parsing mark used to mark these partial matchings.
+  // A mile stone records the starting position in the mark list and number of
+  // marks.
+  struct MileStone {
+    uint16 mark_start;
+    uint16 mark_num;
+  };
+
+  DictList* dict_list_;
+
+  const SpellingTrie *spl_trie_;
+
+  LmaNodeLE0* root_;        // Nodes for root and the first layer.
+  LmaNodeGE1* nodes_ge1_;   // Nodes for other layers.
+
+  // An quick index from spelling id to the LmaNodeLE0 node buffer, or
+  // to the root_ buffer.
+  // Index length:
+  // SpellingTrie::get_instance().get_spelling_num() + 1. The last one is used
+  // to get the end.
+  // All Shengmu ids are not indexed because they will be converted into
+  // corresponding full ids.
+  // So, given an id splid, the son is:
+  // root_[splid_le0_index_[splid - kFullSplIdStart]]
+  uint16 *splid_le0_index_;
+
+  size_t lma_node_num_le0_;
+  size_t lma_node_num_ge1_;
+
+  // The first part is for homophnies, and the last  top_lma_num_ items are
+  // lemmas with highest scores.
+  unsigned char *lma_idx_buf_;
+  size_t lma_idx_buf_len_;  // The total size of lma_idx_buf_ in byte.
+  size_t total_lma_num_;    // Total number of lemmas in this dictionary.
+  size_t top_lmas_num_;     // Number of lemma with highest scores.
+
+  // Parsing mark list used to mark the detailed extended statuses.
+  ParsingMark *parsing_marks_;
+  // The position for next available mark.
+  uint16 parsing_marks_pos_;
+
+  // Mile stone list used to mark the extended status.
+  MileStone *mile_stones_;
+  // The position for the next available mile stone. We use positions (except 0)
+  // as handles.
+  MileStoneHandle mile_stones_pos_;
+
+  // Get the offset of sons for a node.
+  inline size_t get_son_offset(const LmaNodeGE1 *node);
+
+  // Get the offset of homonious ids for a node.
+  inline size_t get_homo_idx_buf_offset(const LmaNodeGE1 *node);
+
+  // Get the lemma id by the offset.
+  inline LemmaIdType get_lemma_id(size_t id_offset);
+
+  void free_resource(bool free_dict_list);
+
+  bool load_dict(FILE *fp);
+
+  // Given a LmaNodeLE0 node, extract the lemmas specified by it, and fill
+  // them into the lpi_items buffer.
+  // This function is called by the search engine.
+  size_t fill_lpi_buffer(LmaPsbItem lpi_items[], size_t max_size,
+                         LmaNodeLE0 *node);
+
+  // Given a LmaNodeGE1 node, extract the lemmas specified by it, and fill
+  // them into the lpi_items buffer.
+  // This function is called by inner functions extend_dict0(), extend_dict1()
+  // and extend_dict2().
+  size_t fill_lpi_buffer(LmaPsbItem lpi_items[], size_t max_size,
+                         size_t homo_buf_off, LmaNodeGE1 *node,
+                         uint16 lma_len);
+
+  // Extend in the trie from level 0.
+  MileStoneHandle extend_dict0(MileStoneHandle from_handle,
+                               const DictExtPara *dep, LmaPsbItem *lpi_items,
+                               size_t lpi_max, size_t *lpi_num);
+
+  // Extend in the trie from level 1.
+  MileStoneHandle extend_dict1(MileStoneHandle from_handle,
+                               const DictExtPara *dep, LmaPsbItem *lpi_items,
+                               size_t lpi_max, size_t *lpi_num);
+
+  // Extend in the trie from level 2.
+  MileStoneHandle extend_dict2(MileStoneHandle from_handle,
+                               const DictExtPara *dep, LmaPsbItem *lpi_items,
+                               size_t lpi_max, size_t *lpi_num);
+
+  // Try to extend the given spelling id buffer, and if the given id_lemma can
+  // be successfully gotten, return true;
+  // The given spelling ids are all valid full ids.
+  bool try_extend(const uint16 *splids, uint16 splid_num, LemmaIdType id_lemma);
+
+#ifdef ___BUILD_MODEL___
+  bool save_dict(FILE *fp);
+#endif  // ___BUILD_MODEL___
+
+  static const int kMaxMileStone = 100;
+  static const int kMaxParsingMark = 600;
+  static const MileStoneHandle kFirstValidMileStoneHandle = 1;
+
+  friend class DictParser;
+  friend class DictBuilder;
+
+ public:
+
+  DictTrie();
+  ~DictTrie();
+
+#ifdef ___BUILD_MODEL___
+  // Construct the tree from the file fn_raw.
+  // fn_validhzs provide the valid hanzi list. If fn_validhzs is
+  // NULL, only chars in GB2312 will be included.
+  bool build_dict(const char *fn_raw, const char *fn_validhzs);
+
+  // Save the binary dictionary
+  // Actually, the SpellingTrie/DictList instance will be also saved.
+  bool save_dict(const char *filename);
+#endif  // ___BUILD_MODEL___
+
+  void convert_to_hanzis(char16 *str, uint16 str_len);
+
+  void convert_to_scis_ids(char16 *str, uint16 str_len);
+
+  // Load a binary dictionary
+  // The SpellingTrie instance/DictList will be also loaded
+  bool load_dict(const char *filename, LemmaIdType start_id,
+                 LemmaIdType end_id);
+  bool load_dict_fd(int sys_fd, long start_offset, long length,
+                    LemmaIdType start_id, LemmaIdType end_id);
+  bool close_dict() {return true;}
+  size_t number_of_lemmas() {return 0;}
+
+  void reset_milestones(uint16 from_step, MileStoneHandle from_handle);
+
+  MileStoneHandle extend_dict(MileStoneHandle from_handle,
+                              const DictExtPara *dep,
+                              LmaPsbItem *lpi_items,
+                              size_t lpi_max, size_t *lpi_num);
+
+  size_t get_lpis(const uint16 *splid_str, uint16 splid_str_len,
+                  LmaPsbItem *lpi_items, size_t lpi_max);
+
+  uint16 get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, uint16 str_max);
+
+  uint16 get_lemma_splids(LemmaIdType id_lemma, uint16 *splids,
+                          uint16 splids_max, bool arg_valid);
+
+  size_t predict(const char16 *last_hzs, uint16 hzs_len,
+                 NPredictItem *npre_items, size_t npre_max,
+                 size_t b4_used);
+
+  LemmaIdType put_lemma(char16 lemma_str[], uint16 splids[],
+                        uint16 lemma_len, uint16 count) {return 0;}
+
+  LemmaIdType update_lemma(LemmaIdType lemma_id, int16 delta_count,
+                           bool selected) {return 0;}
+
+  LemmaIdType get_lemma_id(char16 lemma_str[], uint16 splids[],
+                           uint16 lemma_len) {return 0;}
+
+  LmaScoreType get_lemma_score(LemmaIdType lemma_id) {return 0;}
+
+  LmaScoreType get_lemma_score(char16 lemma_str[], uint16 splids[],
+                        uint16 lemma_len) {return 0;}
+
+  bool remove_lemma(LemmaIdType lemma_id) {return false;}
+
+  size_t get_total_lemma_count() {return 0;}
+  void set_total_lemma_count_of_others(size_t count);
+
+  void flush_cache() {}
+
+  LemmaIdType get_lemma_id(const char16 lemma_str[], uint16 lemma_len);
+
+  // Fill the lemmas with highest scores to the prediction buffer.
+  // his_len is the history length to fill in the prediction buffer.
+  size_t predict_top_lmas(size_t his_len, NPredictItem *npre_items,
+                          size_t npre_max, size_t b4_used);
+};
+}
+
+#endif  // PINYINIME_INCLUDE_DICTTRIE_H__
diff --git a/PinyinIME/jni/include/lpicache.h b/PinyinIME/jni/include/lpicache.h
new file mode 100644
index 0000000..6073597
--- /dev/null
+++ b/PinyinIME/jni/include/lpicache.h
@@ -0,0 +1,62 @@
+/*
+ * 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 PINYINIME_ANDPY_INCLUDE_LPICACHE_H__
+#define PINYINIME_ANDPY_INCLUDE_LPICACHE_H__
+
+#include <stdlib.h>
+#include "./searchutility.h"
+#include "./spellingtrie.h"
+
+namespace ime_pinyin {
+
+// Used to cache LmaPsbItem list for half spelling ids.
+class LpiCache {
+ private:
+  static LpiCache *instance_;
+  static const int kMaxLpiCachePerId = 15;
+
+  LmaPsbItem *lpi_cache_;
+  uint16 *lpi_cache_len_;
+
+ public:
+  LpiCache();
+  ~LpiCache();
+
+  static LpiCache& get_instance();
+
+  // Test if the LPI list of the given splid  has been cached.
+  // If splid is a full spelling id, it returns false, because we only cache
+  // list for half ids.
+  bool is_cached(uint16 splid);
+
+  // Put LPI list to cahce. If the length of the list, lpi_num, is longer than
+  // the cache buffer. the list will be truncated, and function returns the
+  // maximum length of the cache buffer.
+  // Note: splid must be a half id, and lpi_items must be not NULL. The
+  // caller of this function should guarantee this.
+  size_t put_cache(uint16 splid, LmaPsbItem lpi_items[], size_t lpi_num);
+
+  // Get the cached list for the given half id.
+  // Return the length of the cached buffer.
+  // Note: splid must be a half id, and lpi_items must be not NULL. The
+  // caller of this function should guarantee this.
+  size_t get_cache(uint16 splid, LmaPsbItem lpi_items[], size_t lpi_max);
+};
+
+}  // namespace
+
+#endif  // PINYINIME_ANDPY_INCLUDE_LPICACHE_H__
diff --git a/PinyinIME/jni/include/matrixsearch.h b/PinyinIME/jni/include/matrixsearch.h
new file mode 100644
index 0000000..b9cf061
--- /dev/null
+++ b/PinyinIME/jni/include/matrixsearch.h
@@ -0,0 +1,453 @@
+/*
+ * 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 PINYINIME_ANDPY_INCLUDE_MATRIXSEARCH_H__
+#define PINYINIME_ANDPY_INCLUDE_MATRIXSEARCH_H__
+
+#include <stdlib.h>
+#include "./atomdictbase.h"
+#include "./dicttrie.h"
+#include "./searchutility.h"
+#include "./spellingtrie.h"
+#include "./splparser.h"
+
+namespace ime_pinyin {
+
+static const size_t kMaxRowNum = kMaxSearchSteps;
+
+typedef struct {
+  // MileStoneHandle objects for the system and user dictionaries.
+  MileStoneHandle dict_handles[2];
+  // From which DMI node. -1 means it's from root.
+  PoolPosType dmi_fr;
+  // The spelling id for the Pinyin string from the previous DMI to this node.
+  // If it is a half id like Shengmu, the node pointed by dict_node is the first
+  // node with this Shengmu,
+  uint16 spl_id;
+  // What's the level of the dict node. Level of root is 0, but root is never
+  // recorded by dict_node.
+  unsigned char dict_level:7;
+  // If this node is for composing phrase, this bit is 1.
+  unsigned char c_phrase:1;
+  // Whether the spl_id is parsed with a split character at the end.
+  unsigned char splid_end_split:1;
+  // What's the length of the spelling string for this match, for the whole
+  // word.
+  unsigned char splstr_len:7;
+  // Used to indicate whether all spelling ids from the root are full spelling
+  // ids. This information is useful for keymapping mode(not finished). Because
+  // in this mode, there is no clear boundaries, we prefer those results which
+  // have full spelling ids.
+  unsigned char all_full_id:1;
+} DictMatchInfo, *PDictMatchInfo;
+
+typedef struct MatrixNode {
+  LemmaIdType id;
+  float score;
+  MatrixNode *from;
+  // From which DMI node. Used to trace the spelling segmentation.
+  PoolPosType dmi_fr;
+  uint16 step;
+} MatrixNode, *PMatrixNode;
+
+typedef struct {
+  // The MatrixNode position in the matrix pool
+  PoolPosType mtrx_nd_pos;
+  // The DictMatchInfo position in the DictMatchInfo pool.
+  PoolPosType dmi_pos;
+  uint16 mtrx_nd_num;
+  uint16 dmi_num:15;
+  // Used to indicate whether there are dmi nodes in this step with full
+  // spelling id. This information is used to decide whether a substring of a
+  // valid Pinyin should be extended.
+  //
+  // Example1: shoudao
+  // When the last char 'o' is added, the parser will find "dao" is a valid
+  // Pinyin, and because all dmi nodes at location 'd' (including those for
+  // "shoud", and those for "d") have Shengmu id only, so it is not necessary
+  // to extend "ao", otherwise the result may be "shoud ao", that is not
+  // reasonable.
+  //
+  // Example2: hengao
+  // When the last 'o' is added, the parser finds "gao" is a valid Pinyin.
+  // Because some dmi nodes at 'g' has Shengmu ids (hen'g and g), but some dmi
+  // nodes at 'g' has full ids ('heng'), so it is necessary to extend "ao", thus
+  // "heng ao" can also be the result.
+  //
+  // Similarly, "ganga" is expanded to "gang a".
+  //
+  // For Pinyin string "xian", because "xian" is a valid Pinyin, because all dmi
+  // nodes at 'x' only have Shengmu ids, the parser will not try "x ian" (and it
+  // is not valid either). If the parser uses break in the loop, the result
+  // always be "xian"; but if the parser uses continue in the loop, "xi an" will
+  // also be tried. This behaviour can be set via the function
+  // set_xi_an_switch().
+  uint16 dmi_has_full_id:1;
+  // Points to a MatrixNode of the current step to indicate which choice the
+  // user selects.
+  MatrixNode *mtrx_nd_fixed;
+} MatrixRow, *PMatrixRow;
+
+// When user inputs and selects candidates, the fixed lemma ids are stored in
+// lma_id_ of class MatrixSearch, and fixed_lmas_ is used to indicate how many
+// lemmas from the beginning are fixed. If user deletes Pinyin characters one
+// by one from the end, these fixed lemmas can be unlocked one by one when
+// necessary. Whenever user deletes a Chinese character and its spelling string
+// in these fixed lemmas, all fixed lemmas will be merged together into a unit
+// named ComposingPhrase with a lemma id kLemmaIdComposing, and this composing
+// phrase will be the first lemma in the sentence. Because it contains some
+// modified lemmas (by deleting a character), these merged lemmas are called
+// sub lemmas (sublma), and each of them are represented individually, so that
+// when user deletes Pinyin characters from the end, these sub lemmas can also
+// be unlocked one by one.
+typedef struct {
+  uint16 spl_ids[kMaxRowNum];
+  uint16 spl_start[kMaxRowNum];
+  char16 chn_str[kMaxRowNum];       // Chinese string.
+  uint16 sublma_start[kMaxRowNum];  // Counted in Chinese characters.
+  size_t sublma_num;
+  uint16 length;                    // Counted in Chinese characters.
+} ComposingPhrase, *TComposingPhrase;
+
+class MatrixSearch {
+ private:
+  // If it is true, prediction list by string whose length is greater than 1
+  // will be limited to a reasonable number.
+  static const bool kPredictLimitGt1 = false;
+
+  // If it is true, the engine will prefer long history based prediction,
+  // for example, when user inputs "BeiJing", we prefer "DaXue", etc., which are
+  // based on the two-character history.
+  static const bool kPreferLongHistoryPredict = true;
+
+  // If it is true, prediction will only be based on user dictionary. this flag
+  // is for debug purpose.
+  static const bool kOnlyUserDictPredict = false;
+
+  // The maximum buffer to store LmaPsbItems.
+  static const size_t kMaxLmaPsbItems = 1450;
+
+  // How many rows for each step.
+  static const size_t kMaxNodeARow = 5;
+
+  // The maximum length of the sentence candidates counted in chinese
+  // characters
+  static const size_t kMaxSentenceLength = 16;
+
+  // The size of the matrix node pool.
+  static const size_t kMtrxNdPoolSize = 200;
+
+  // The size of the DMI node pool.
+  static const size_t kDmiPoolSize = 800;
+
+  // Used to indicate whether this object has been initialized.
+  bool inited_;
+
+  // Spelling trie.
+  const SpellingTrie *spl_trie_;
+
+  // Used to indicate this switcher status: when "xian" is parseed, should
+  // "xi an" also be extended. Default is false.
+  // These cases include: xia, xian, xiang, zhuan, jiang..., etc. The string
+  // should be valid for a FULL spelling, or a combination of two spellings,
+  // first of which is a FULL id too. So even it is true, "da" will never be
+  // split into "d a", because "d" is not a full spelling id.
+  bool xi_an_enabled_;
+
+  // System dictionary.
+  DictTrie* dict_trie_;
+
+  // User dictionary.
+  AtomDictBase* user_dict_;
+
+  // Spelling parser.
+  SpellingParser* spl_parser_;
+
+  // The maximum allowed length of spelling string (such as a Pinyin string).
+  size_t max_sps_len_;
+
+  // The maximum allowed length of a result Chinese string.
+  size_t max_hzs_len_;
+
+  // Pinyin string. Max length: kMaxRowNum - 1
+  char pys_[kMaxRowNum];
+
+  // The length of the string that has been decoded successfully.
+  size_t pys_decoded_len_;
+
+  // Shared buffer for multiple purposes.
+  size_t *share_buf_;
+
+  MatrixNode *mtrx_nd_pool_;
+  PoolPosType mtrx_nd_pool_used_;    // How many nodes used in the pool
+  DictMatchInfo *dmi_pool_;
+  PoolPosType dmi_pool_used_;        // How many items used in the pool
+
+  MatrixRow *matrix_;                // The first row is for starting
+
+  DictExtPara *dep_;                 // Parameter used to extend DMI nodes.
+
+  NPredictItem *npre_items_;         // Used to do prediction
+  size_t npre_items_len_;
+
+  // The starting positions and lemma ids for the full sentence candidate.
+  size_t lma_id_num_;
+  uint16 lma_start_[kMaxRowNum];     // Counted in spelling ids.
+  LemmaIdType lma_id_[kMaxRowNum];
+  size_t fixed_lmas_;
+
+  // If fixed_lmas_ is bigger than i,  Element i is used to indicate whether
+  // the i'th lemma id in lma_id_ is the first candidate for that step.
+  // If all candidates are the first one for that step, the whole string can be
+  // decoded by the engine automatically, so no need to add it to user
+  // dictionary. (We are considering to add it to user dictionary in the
+  // future).
+  uint8 fixed_lmas_no1_[kMaxRowNum];
+
+  // Composing phrase
+  ComposingPhrase c_phrase_;
+
+  // If dmi_c_phrase_ is true, the decoder will try to match the
+  // composing phrase (And definitely it will match successfully). If it
+  // is false, the decoder will try to match lemmas items in dictionaries.
+  bool dmi_c_phrase_;
+
+  // The starting positions and spelling ids for the first full sentence
+  // candidate.
+  size_t spl_id_num_;                // Number of splling ids
+  uint16 spl_start_[kMaxRowNum];     // Starting positions
+  uint16 spl_id_[kMaxRowNum];        // Spelling ids
+  // Used to remember the last fixed position, counted in Hanzi.
+  size_t fixed_hzs_;
+
+  // Lemma Items with possibility score, two purposes:
+  // 1. In Viterbi decoding, this buffer is used to get all possible candidates
+  // for current step;
+  // 2. When the search is done, this buffer is used to get candiates from the
+  // first un-fixed step and show them to the user.
+  LmaPsbItem lpi_items_[kMaxLmaPsbItems];
+  size_t lpi_total_;
+
+  // Assign the pointers with NULL. The caller makes sure that all pointers are
+  // not valid before calling it. This function only will be called in the
+  // construction function and free_resource().
+  void reset_pointers_to_null();
+
+  bool alloc_resource();
+
+  void free_resource();
+
+  // Reset the search space from ch_pos step. For example, if the original
+  // input Pinyin is "an", reset_search(1) will reset the search space to the
+  // result of "a". If the given position is out of range, return false.
+  // if clear_fixed_this_step is true, and the ch_pos step is a fixed step,
+  // clear its fixed status. if clear_dmi_his_step is true, clear the DMI nodes.
+  // If clear_mtrx_this_sTep is true, clear the mtrx nodes of this step.
+  // The DMI nodes will be kept.
+  //
+  // Note: this function should not destroy content of pys_.
+  bool reset_search(size_t ch_pos, bool clear_fixed_this_step,
+                    bool clear_dmi_this_step, bool clear_mtrx_this_step);
+
+  // Delete a part of the content in pys_.
+  void del_in_pys(size_t start, size_t len);
+
+  // Delete a spelling id and its corresponding Chinese character, and merge
+  // the fixed lemmas into the composing phrase.
+  // del_spl_pos indicates which spelling id needs to be delete.
+  // This function will update the lemma and spelling segmentation information.
+  // The caller guarantees that fixed_lmas_ > 0 and del_spl_pos is within
+  // the fixed lemmas.
+  void merge_fixed_lmas(size_t del_spl_pos);
+
+  // Get spelling start posistions and ids. The result will be stored in
+  // spl_id_num_, spl_start_[], spl_id_[].
+  // fixed_hzs_ will be also assigned.
+  void get_spl_start_id();
+
+  // Get all lemma ids with match the given spelling id stream(shorter than the
+  // maximum length of a word).
+  // If pfullsent is not NULL, means the full sentence candidate may be the
+  // same with the coming lemma string, if so, remove that lemma.
+  // The result is sorted in descendant order by the frequency score.
+  size_t get_lpis(const uint16* splid_str, size_t splid_str_len,
+                  LmaPsbItem* lma_buf, size_t max_lma_buf,
+                  const char16 *pfullsent, bool sort_by_psb);
+
+  uint16 get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, uint16 str_max);
+
+  uint16 get_lemma_splids(LemmaIdType id_lemma, uint16 *splids,
+                          uint16 splids_max, bool arg_valid);
+
+
+  // Extend a DMI node with a spelling id. ext_len is the length of the rows
+  // to extend, actually, it is the size of the spelling string of splid.
+  // return value can be 1 or 0.
+  // 1 means a new DMI is filled in (dmi_pool_used_ is the next blank DMI in
+  // the pool).
+  // 0 means either the dmi node can not be extended with splid, or the splid
+  // is a Shengmu id, which is only used to get lpi_items, or the result node
+  // in DictTrie has no son, it is not nccessary to keep the new DMI.
+  //
+  // This function modifies the content of lpi_items_ and lpi_total_.
+  // lpi_items_ is used to get the LmaPsbItem list, lpi_total_ returns the size.
+  // The function's returned value has no relation with the value of lpi_num.
+  //
+  // If dmi == NULL, this function will extend the root node of DictTrie
+  //
+  // This function will not change dmi_nd_pool_used_. Please change it after
+  // calling this function if necessary.
+  //
+  // The caller should guarantees that NULL != dep.
+  size_t extend_dmi(DictExtPara *dep, DictMatchInfo *dmi_s);
+
+  // Extend dmi for the composing phrase.
+  size_t extend_dmi_c(DictExtPara *dep, DictMatchInfo *dmi_s);
+
+  // Extend a MatrixNode with the give LmaPsbItem list.
+  // res_row is the destination row number.
+  // This function does not change mtrx_nd_pool_used_. Please change it after
+  // calling this function if necessary.
+  // return 0 always.
+  size_t extend_mtrx_nd(MatrixNode *mtrx_nd, LmaPsbItem lpi_items[],
+                        size_t lpi_num, PoolPosType dmi_fr, size_t res_row);
+
+
+  // Try to find a dmi node at step_to position, and the found dmi node should
+  // match the given spelling id strings.
+  PoolPosType match_dmi(size_t step_to, uint16 spl_ids[], uint16 spl_id_num);
+
+  bool add_char(char ch);
+  bool prepare_add_char(char ch);
+
+  // Called after prepare_add_char, so the input char has been saved.
+  bool add_char_qwerty();
+
+  // Prepare candidates from the last fixed hanzi position.
+  void prepare_candidates();
+
+  // Is the character in step pos a splitter character?
+  // The caller guarantees that the position is valid.
+  bool is_split_at(uint16 pos);
+
+  void fill_dmi(DictMatchInfo *dmi, MileStoneHandle *handles,
+                PoolPosType dmi_fr,
+                uint16 spl_id, uint16 node_num, unsigned char dict_level,
+                bool splid_end_split, unsigned char splstr_len,
+                unsigned char all_full_id);
+
+  size_t inner_predict(const char16 fixed_scis_ids[], uint16 scis_num,
+                       char16 predict_buf[][kMaxPredictSize + 1],
+                       size_t buf_len);
+
+  // Add the first candidate to the user dictionary.
+  bool try_add_cand0_to_userdict();
+
+  // Add a user lemma to the user dictionary. This lemma is a subset of
+  // candidate 0. lma_from is from which lemma in lma_ids_, lma_num is the
+  // number of lemmas to be combined together as a new lemma. The caller
+  // gurantees that the combined new lemma's length is less or equal to
+  // kMaxLemmaSize.
+  bool add_lma_to_userdict(uint16 lma_from, uint16 lma_num, float score);
+
+  // Update dictionary frequencies.
+  void update_dict_freq();
+
+  void debug_print_dmi(PoolPosType dmi_pos, uint16 nest_level);
+
+ public:
+  MatrixSearch();
+  ~MatrixSearch();
+
+  bool init(const char *fn_sys_dict, const char *fn_usr_dict);
+
+  bool init_fd(int sys_fd, long start_offset, long length,
+               const char *fn_usr_dict);
+
+  void set_max_lens(size_t max_sps_len, size_t max_hzs_len);
+
+  void close();
+
+  void flush_cache();
+
+  void set_xi_an_switch(bool xi_an_enabled);
+
+  bool get_xi_an_switch();
+
+  // Reset the search space. Equivalent to reset_search(0).
+  // If inited, always return true;
+  bool reset_search();
+
+  // Search a Pinyin string.
+  // Return value is the position successfully parsed.
+  size_t search(const char *py, size_t py_len);
+
+  // Used to delete something in the Pinyin string kept by the engine, and do
+  // a re-search.
+  // Return value is the new length of Pinyin string kept by the engine which
+  // is parsed successfully.
+  // If is_pos_in_splid is false, pos is used to indicate that pos-th Pinyin
+  // character needs to be deleted. If is_pos_in_splid is true, all Pinyin
+  // characters for pos-th spelling id needs to be deleted.
+  // If the deleted character(s) is just after a fixed lemma or sub lemma in
+  // composing phrase, clear_fixed_this_step indicates whether we needs to
+  // unlock the last fixed lemma or sub lemma.
+  // If is_pos_in_splid is false, and pos-th character is in the range for the
+  // fixed lemmas or composing string, this function will do nothing and just
+  // return the result of the previous search.
+  size_t delsearch(size_t pos, bool is_pos_in_splid,
+                   bool clear_fixed_this_step);
+
+  // Get the number of candiates, called after search().
+  size_t get_candidate_num();
+
+  // Get the Pinyin string stored by the engine.
+  // *decoded_len returns the length of the successfully decoded string.
+  const char* get_pystr(size_t *decoded_len);
+
+  // Get the spelling boundaries for the first sentence candidate.
+  // Number of spellings will be returned. The number of valid elements in
+  // spl_start is one more than the return value because the last one is used
+  // to indicate the beginning of the next un-input speling.
+  // For a Pinyin "women", the returned value is 2, spl_start is [0, 2, 5] .
+  size_t get_spl_start(const uint16 *&spl_start);
+
+  // Get one candiate string. If full sentence candidate is available, it will
+  // be the first one.
+  char16* get_candidate(size_t cand_id, char16 *cand_str, size_t max_len);
+
+  // Get the first candiate, which is a "full sentence".
+  // retstr_len is not NULL, it will be used to return the string length.
+  // If only_unfixed is true, only unfixed part will be fetched.
+  char16* get_candidate0(char16* cand_str, size_t max_len,
+                         uint16 *retstr_len, bool only_unfixed);
+
+  // Choose a candidate. The decoder will do a search after the fixed position.
+  size_t choose(size_t cand_id);
+
+  // Cancel the last choosing operation, and return the new number of choices.
+  size_t cancel_last_choice();
+
+  // Get the length of fixed Hanzis.
+  size_t get_fixedlen();
+
+  size_t get_predicts(const char16 fixed_buf[],
+                      char16 predict_buf[][kMaxPredictSize + 1],
+                      size_t buf_len);
+};
+}
+
+#endif  // PINYINIME_ANDPY_INCLUDE_MATRIXSEARCH_H__
diff --git a/PinyinIME/jni/include/mystdlib.h b/PinyinIME/jni/include/mystdlib.h
new file mode 100644
index 0000000..dfcf980
--- /dev/null
+++ b/PinyinIME/jni/include/mystdlib.h
@@ -0,0 +1,32 @@
+/*
+ * 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 PINYINIME_INCLUDE_MYSTDLIB_H__
+#define PINYINIME_INCLUDE_MYSTDLIB_H__
+
+#include <stdlib.h>
+
+namespace ime_pinyin {
+
+void myqsort(void *p, size_t n, size_t es,
+             int (*cmp)(const void *, const void *));
+
+void *mybsearch(const void *key, const void *base,
+                size_t nmemb, size_t size,
+                int (*compar)(const void *, const void *));
+}
+
+#endif  // PINYINIME_INCLUDE_MYSTDLIB_H__
diff --git a/PinyinIME/jni/include/ngram.h b/PinyinIME/jni/include/ngram.h
new file mode 100644
index 0000000..ad6c304
--- /dev/null
+++ b/PinyinIME/jni/include/ngram.h
@@ -0,0 +1,96 @@
+/*
+ * 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 PINYINIME_INCLUDE_NGRAM_H__
+#define PINYINIME_INCLUDE_NGRAM_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "./dictdef.h"
+
+namespace ime_pinyin {
+
+typedef unsigned char CODEBOOK_TYPE;
+
+static const size_t kCodeBookSize = 256;
+
+class NGram {
+ public:
+  // The maximum score of a lemma item.
+  static const LmaScoreType kMaxScore = 0x3fff;
+
+  // In order to reduce the storage size, the original log value is amplified by
+  // kScoreAmplifier, and we use LmaScoreType to store.
+  // After this process, an item with a lower score has a higher frequency.
+  static const int kLogValueAmplifier = -800;
+
+  // System words' total frequency. It is not the real total frequency, instead,
+  // It is only used to adjust system lemmas' scores when the user dictionary's
+  // total frequency changes.
+  // In this version, frequencies of system lemmas are fixed. We are considering
+  // to make them changable in next version.
+  static const size_t kSysDictTotalFreq = 100000000;
+
+ private:
+
+  static NGram* instance_;
+
+  bool initialized_;
+  size_t idx_num_;
+
+  size_t total_freq_none_sys_;
+
+  // Score compensation for system dictionary lemmas.
+  // Because after user adds some user lemmas, the total frequency changes, and
+  // we use this value to normalize the score.
+  float sys_score_compensation_;
+
+#ifdef ___BUILD_MODEL___
+  double *freq_codes_df_;
+#endif
+  LmaScoreType *freq_codes_;
+  CODEBOOK_TYPE *lma_freq_idx_;
+
+ public:
+  NGram();
+  ~NGram();
+
+  static NGram& get_instance();
+
+  bool save_ngram(FILE *fp);
+  bool load_ngram(FILE *fp);
+
+  // Set the total frequency of all none system dictionaries.
+  void set_total_freq_none_sys(size_t freq_none_sys);
+
+  float get_uni_psb(LemmaIdType lma_id);
+
+  // Convert a probability to score. Actually, the score will be limited to
+  // kMaxScore, but at runtime, we also need float expression to get accurate
+  // value of the score.
+  // After the conversion, a lower score indicates a higher probability of the
+  // item.
+  static float convert_psb_to_score(double psb);
+
+#ifdef ___BUILD_MODEL___
+  // For constructing the unigram mode model.
+  bool build_unigram(LemmaEntry *lemma_arr, size_t num,
+                     LemmaIdType next_idx_unused);
+#endif
+};
+}
+
+#endif  // PINYINIME_INCLUDE_NGRAM_H__
diff --git a/PinyinIME/jni/include/pinyinime.h b/PinyinIME/jni/include/pinyinime.h
new file mode 100644
index 0000000..0744ec7
--- /dev/null
+++ b/PinyinIME/jni/include/pinyinime.h
@@ -0,0 +1,211 @@
+/*
+ * 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 PINYINIME_INCLUDE_ANDPYIME_H__
+#define PINYINIME_INCLUDE_ANDPYIME_H__
+
+#include <stdlib.h>
+#include "./dictdef.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  namespace ime_pinyin {
+
+  /**
+   * Open the decoder engine via the system and user dictionary file names.
+   *
+   * @param fn_sys_dict The file name of the system dictionary.
+   * @param fn_usr_dict The file name of the user dictionary.
+   * @return true if open the decoder engine successfully.
+   */
+  bool im_open_decoder(const char *fn_sys_dict, const char *fn_usr_dict);
+
+  /**
+   * Open the decoder engine via the system dictionary FD and user dictionary
+   * file name. Because on Android, the system dictionary is embedded in the
+   * whole application apk file.
+   *
+   * @param sys_fd The file in which the system dictionary is embedded.
+   * @param start_offset The starting position of the system dictionary in the
+   * file sys_fd.
+   * @param length The length of the system dictionary in the file sys_fd,
+   * counted in byte.
+   * @return true if succeed.
+   */
+  bool im_open_decoder_fd(int sys_fd, long start_offset, long length,
+                          const char *fn_usr_dict);
+
+  /**
+   * Close the decoder engine.
+   */
+  void im_close_decoder();
+
+  /**
+   * Set maximum limitations for decoding. If this function is not called,
+   * default values will be used. For example, due to screen size limitation,
+   * the UI engine of the IME can only show a certain number of letters(input)
+   * to decode, and a certain number of Chinese characters(output). If after
+   * user adds a new letter, the input or the output string is longer than the
+   * limitations, the engine will discard the recent letter.
+   *
+   * @param max_sps_len Maximum length of the spelling string(Pinyin string).
+   * @max_hzs_len Maximum length of the decoded Chinese character string.
+   */
+  void im_set_max_lens(size_t max_sps_len, size_t max_hzs_len);
+
+  /**
+   * Flush cached data to persistent memory. Because at runtime, in order to
+   * achieve best performance, some data is only store in memory.
+   */
+  void im_flush_cache();
+
+  /**
+   * Use a spelling string(Pinyin string) to search. The engine will try to do
+   * an incremental search based on its previous search result, so if the new
+   * string has the same prefix with the previous one stored in the decoder,
+   * the decoder will only continue the search from the end of the prefix.
+   * If the caller needs to do a brand new search, please call im_reset_search()
+   * first. Calling im_search() is equivalent to calling im_add_letter() one by
+   * one.
+   *
+   * @param sps_buf The spelling string buffer to decode.
+   * @param sps_len The length of the spelling string buffer.
+   * @return The number of candidates.
+   */
+  size_t im_search(const char* sps_buf, size_t sps_len);
+
+  /**
+   * Make a delete operation in the current search result, and make research if
+   * necessary.
+   *
+   * @param pos The posistion of char in spelling string to delete, or the
+   * position of spelling id in result string to delete.
+   * @param is_pos_in_splid Indicate whether the pos parameter is the position
+   * in the spelling string, or the position in the result spelling id string.
+   * @return The number of candidates.
+   */
+  size_t im_delsearch(size_t pos, bool is_pos_in_splid,
+                      bool clear_fixed_this_step);
+
+  /**
+   * Reset the previous search result.
+   */
+  void im_reset_search();
+
+  /**
+   * Add a Pinyin letter to the current spelling string kept by decoder. If the
+   * decoder fails in adding the letter, it will do nothing. im_get_sps_str()
+   * can be used to get the spelling string kept by decoder currently.
+   *
+   * @param ch The letter to add.
+   * @return The number of candidates.
+   */
+  size_t im_add_letter(char ch);
+
+  /**
+   * Get the spelling string kept by the decoder.
+   *
+   * @param decoded_len Used to return how many characters in the spelling
+   * string is successfully parsed.
+   * @return The spelling string kept by the decoder.
+   */
+  const char *im_get_sps_str(size_t *decoded_len);
+
+  /**
+   * Get a candidate(or choice) string.
+   *
+   * @param cand_id The id to get a candidate. Started from 0. Usually, id 0
+   * is a sentence-level candidate.
+   * @param cand_str The buffer to store the candidate.
+   * @param max_len The maximum length of the buffer.
+   * @return cand_str if succeeds, otherwise NULL.
+   */
+  char16* im_get_candidate(size_t cand_id, char16* cand_str,
+                           size_t max_len);
+
+  /**
+   * Get the segmentation information(the starting positions) of the spelling
+   * string.
+   *
+   * @param spl_start Used to return the starting posistions.
+   * @return The number of spelling ids. If it is L, there will be L+1 valid
+   * elements in spl_start, and spl_start[L] is the posistion after the end of
+   * the last spelling id.
+   */
+  size_t im_get_spl_start_pos(const uint16 *&spl_start);
+
+  /**
+   * Choose a candidate and make it fixed. If the candidate does not match
+   * the end of all spelling ids, new candidates will be provided from the
+   * first unfixed position. If the candidate matches the end of the all
+   * spelling ids, there will be only one new candidates, or the whole fixed
+   * sentence.
+   *
+   * @param cand_id The id of candidate to select and make it fixed.
+   * @return The number of candidates. If after the selection, the whole result
+   * string has been fixed, there will be only one candidate.
+   */
+  size_t im_choose(size_t cand_id);
+
+  /**
+   * Cancel the last selection, or revert the last operation of im_choose().
+   *
+   * @return The number of candidates.
+   */
+  size_t im_cancel_last_choice();
+
+  /**
+   * Get the number of fixed spelling ids, or Chinese characters.
+   *
+   * @return The number of fixed spelling ids, of Chinese characters.
+   */
+  size_t im_get_fixed_len();
+
+  /**
+   * Cancel the input state and reset the search workspace.
+   */
+  bool im_cancel_input();
+
+  /**
+   * Get prediction candiates based on the given fixed Chinese string as the
+   * history.
+   *
+   * @param his_buf The history buffer to do the prediction. It should be ended
+   * with '\0'.
+   * @param pre_buf Used to return prediction result list.
+   * @return The number of predicted result string.
+   */
+  size_t im_get_predicts(const char16 *his_buf,
+                         char16 (*&pre_buf)[kMaxPredictSize + 1]);
+
+  /**
+   * Enable Shengmus in ShouZiMu mode.
+   */
+  void im_enable_shm_as_szm(bool enable);
+
+  /**
+   * Enable Yunmus in ShouZiMu mode.
+   */
+  void im_enable_ym_as_szm(bool enable);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // PINYINIME_INCLUDE_ANDPYIME_H__
diff --git a/PinyinIME/jni/include/searchutility.h b/PinyinIME/jni/include/searchutility.h
new file mode 100644
index 0000000..f135710
--- /dev/null
+++ b/PinyinIME/jni/include/searchutility.h
@@ -0,0 +1,142 @@
+/*
+ * 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 PINYINIME_ANDPY_INCLUDE_SEARCHCOMMON_H__
+#define PINYINIME_ANDPY_INCLUDE_SEARCHCOMMON_H__
+
+#include <stdlib.h>
+#include "./spellingtrie.h"
+
+namespace ime_pinyin {
+
+// Type used to identify the size of a pool, such as id pool, etc.
+typedef uint16 PoolPosType;
+
+// Type used to identify a parsing mile stone in an atom dictionary.
+typedef uint16 MileStoneHandle;
+
+// Type used to express a lemma and its probability score.
+typedef struct {
+  size_t id:(kLemmaIdSize * 8);
+  size_t lma_len:4;
+  uint16 psb;  // The score, the lower psb, the higher possibility.
+  // For single character items, we may also need Hanzi.
+  // For multiple characer items, ignore it.
+  char16 hanzi;
+} LmaPsbItem, *PLmaPsbItem;
+
+// LmaPsbItem extended with string.
+typedef struct {
+  LmaPsbItem lpi;
+  char16 str[kMaxLemmaSize + 1];
+} LmaPsbStrItem, *PLmaPsbStrItem;
+
+
+typedef struct {
+  float psb;
+  char16 pre_hzs[kMaxPredictSize];
+  uint16 his_len;  // The length of the history used to do the prediction.
+} NPredictItem, *PNPredictItem;
+
+// Parameter structure used to extend in a dictionary. All dictionaries
+// receives the same DictExtPara and a dictionary specific MileStoneHandle for
+// extending.
+//
+// When the user inputs a new character, AtomDictBase::extend_dict() will be
+// called at least once for each dictionary.
+//
+// For example, when the user inputs "wm", extend_dict() will be called twice,
+// and the DictExtPara parameter are as follows respectively:
+// 1. splids = {w, m}; splids_extended = 1; ext_len = 1; step_no = 1;
+// splid_end_split = false; id_start = wa(the first id start with 'w');
+// id_num = number of ids starting with 'w'.
+// 2. splids = {m}; splids_extended = 0; ext_len = 1; step_no = 1;
+// splid_end_split = false; id_start = wa; id_num = number of ids starting with
+// 'w'.
+//
+// For string "women", one of the cases of the DictExtPara parameter is:
+// splids = {wo, men}, splids_extended = 1, ext_len = 3 (length of "men"),
+// step_no = 4; splid_end_split = false; id_start = men, id_num = 1.
+//
+typedef struct {
+  // Spelling ids for extending, there are splids_extended + 1 ids in the
+  // buffer.
+  // For a normal lemma, there can only be kMaxLemmaSize spelling ids in max,
+  // but for a composing phrase, there can kMaxSearchSteps spelling ids.
+  uint16 splids[kMaxSearchSteps];
+
+  // Number of ids that have been used before. splids[splids_extended] is the
+  // newly added id for the current extension.
+  uint16 splids_extended;
+
+  // The step span of the extension. It is also the size of the string for
+  // the newly added spelling id.
+  uint16 ext_len;
+
+  // The step number for the current extension. It is also the ending position
+  // in the input Pinyin string for the substring of spelling ids in splids[].
+  // For example, when the user inputs "women", step_no = 4.
+  // This parameter may useful to manage the MileStoneHandle list for each
+  // step. When the user deletes a character from the string, MileStoneHandle
+  // objects for the the steps after that character should be reset; when the
+  // user begins a new string, all MileStoneHandle objects should be reset.
+  uint16 step_no;
+
+  // Indicate whether the newly added spelling ends with a splitting character
+  bool splid_end_split;
+
+  // If the newly added id is a half id, id_start is the first id of the
+  // corresponding full ids; if the newly added id is a full id, id_start is
+  // that id.
+  uint16 id_start;
+
+  // If the newly added id is a half id, id_num is the number of corresponding
+  // ids; if it is a full id, id_num == 1.
+  uint16 id_num;
+}DictExtPara, *PDictExtPara;
+
+bool is_system_lemma(LemmaIdType lma_id);
+bool is_user_lemma(LemmaIdType lma_id);
+bool is_composing_lemma(LemmaIdType lma_id);
+
+int cmp_lpi_with_psb(const void *p1, const void *p2);
+int cmp_lpi_with_unified_psb(const void *p1, const void *p2);
+int cmp_lpi_with_id(const void *p1, const void *p2);
+int cmp_lpi_with_hanzi(const void *p1, const void *p2);
+
+int cmp_lpsi_with_str(const void *p1, const void *p2);
+
+int cmp_hanzis_1(const void *p1, const void *p2);
+int cmp_hanzis_2(const void *p1, const void *p2);
+int cmp_hanzis_3(const void *p1, const void *p2);
+int cmp_hanzis_4(const void *p1, const void *p2);
+int cmp_hanzis_5(const void *p1, const void *p2);
+int cmp_hanzis_6(const void *p1, const void *p2);
+int cmp_hanzis_7(const void *p1, const void *p2);
+int cmp_hanzis_8(const void *p1, const void *p2);
+
+int cmp_npre_by_score(const void *p1, const void *p2);
+int cmp_npre_by_hislen_score(const void *p1, const void *p2);
+int cmp_npre_by_hanzi_score(const void *p1, const void *p2);
+
+
+size_t remove_duplicate_npre(NPredictItem *npre_items, size_t npre_num);
+
+size_t align_to_size_t(size_t size);
+
+}  // namespace
+
+#endif  // PINYINIME_ANDPY_INCLUDE_SEARCHCOMMON_H__
diff --git a/PinyinIME/jni/include/spellingtable.h b/PinyinIME/jni/include/spellingtable.h
new file mode 100644
index 0000000..fd79c6e
--- /dev/null
+++ b/PinyinIME/jni/include/spellingtable.h
@@ -0,0 +1,111 @@
+/*
+ * 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 PINYINIME_INCLUDE_SPELLINGTABLE_H__
+#define PINYINIME_INCLUDE_SPELLINGTABLE_H__
+
+#include <stdlib.h>
+#include "./dictdef.h"
+
+namespace ime_pinyin {
+
+#ifdef ___BUILD_MODEL___
+
+const size_t kMaxSpellingSize = kMaxPinyinSize;
+
+typedef struct {
+  char str[kMaxSpellingSize + 1];
+  double freq;
+} RawSpelling, *PRawSpelling;
+
+// This class is used to store the spelling strings
+// The length of the input spelling string should be less or equal to the
+// spelling_size_ (set by init_table). If the input string is too long,
+// we only keep its first spelling_size_ chars.
+class SpellingTable {
+ private:
+  static const size_t kNotSupportNum = 3;
+  static const char kNotSupportList[kNotSupportNum][kMaxSpellingSize + 1];
+
+  bool need_score_;
+
+  size_t spelling_max_num_;
+
+  RawSpelling *raw_spellings_;
+
+  // Used to store spelling strings. If the spelling table needs to calculate
+  // score, an extra char after each spelling string is the score.
+  // An item with a lower score has a higher probability.
+  char *spelling_buf_;
+  size_t spelling_size_;
+
+  double total_freq_;
+
+  size_t spelling_num_;
+
+  double score_amplifier_;
+
+  unsigned char average_score_;
+
+  // If frozen is true, put_spelling() and contain() are not allowed to call.
+  bool frozen_;
+
+  size_t get_hash_pos(const char* spelling_str);
+  size_t hash_pos_next(size_t hash_pos);
+  void free_resource();
+ public:
+  SpellingTable();
+  ~SpellingTable();
+
+  // pure_spl_size is the pure maximum spelling string size. For example,
+  // "zhuang" is the longgest item in Pinyin, so pure_spl_size should be 6.
+  // spl_max_num is the maximum number of spelling strings to store.
+  // need_score is used to indicate whether the caller needs to calculate a
+  // score for each spelling.
+  bool init_table(size_t pure_spl_size, size_t spl_max_num, bool need_score);
+
+  // Put a spelling string to the table.
+  // It always returns false if called after arrange() withtout a new
+  // init_table() operation.
+  // freq is the spelling's occuring count.
+  // If the spelling has been in the table, occuring count will accumulated.
+  bool put_spelling(const char* spelling_str, double spl_count);
+
+  // Test whether a spelling string is in the table.
+  // It always returns false, when being called after arrange() withtout a new
+  // init_table() operation.
+  bool contain(const char* spelling_str);
+
+  // Sort the spelling strings and put them from the begin of the buffer.
+  // Return the pointer of the sorted spelling strings.
+  // item_size and spl_num return the item size and number of spelling.
+  // Because each spelling uses a '\0' as terminator, the returned item_size is
+  // at least one char longer than the spl_size parameter specified by
+  // init_table(). If the table is initialized to calculate score, item_size
+  // will be increased by 1, and current_spl_str[item_size - 1] stores an
+  // unsinged char score.
+  // An item with a lower score has a higher probability.
+  // Do not call put_spelling() and contains() after arrange().
+  const char* arrange(size_t *item_size, size_t *spl_num);
+
+  float get_score_amplifier();
+
+  unsigned char get_average_score();
+};
+#endif  // ___BUILD_MODEL___
+}
+
+#endif  // PINYINIME_INCLUDE_SPELLINGTABLE_H__
diff --git a/PinyinIME/jni/include/spellingtrie.h b/PinyinIME/jni/include/spellingtrie.h
new file mode 100644
index 0000000..4438757
--- /dev/null
+++ b/PinyinIME/jni/include/spellingtrie.h
@@ -0,0 +1,258 @@
+/*
+ * 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 PINYINIME_INCLUDE_SPELLINGTRIE_H__
+#define PINYINIME_INCLUDE_SPELLINGTRIE_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "./dictdef.h"
+
+namespace ime_pinyin {
+
+static const unsigned short kFullSplIdStart = kHalfSpellingIdNum + 1;
+
+// Node used for the trie of spellings
+struct SpellingNode {
+  SpellingNode *first_son;
+  // The spelling id for each node. If you need more bits to store
+  // spelling id, please adjust this structure.
+  uint16 spelling_idx:11;
+  uint16  num_of_son:5;
+  char char_this_node;
+  unsigned char score;
+};
+
+class SpellingTrie {
+ private:
+  static const int kMaxYmNum = 64;
+  static const size_t kValidSplCharNum = 26;
+
+  static const uint16 kHalfIdShengmuMask = 0x01;
+  static const uint16 kHalfIdYunmuMask = 0x02;
+  static const uint16 kHalfIdSzmMask = 0x04;
+
+  // Map from half spelling id to single char.
+  // For half ids of Zh/Ch/Sh, map to z/c/s (low case) respectively.
+  // For example, 1 to 'A', 2 to 'B', 3 to 'C', 4 to 'c', 5 to 'D', ...,
+  // 28 to 'Z', 29 to 'z'.
+  // [0] is not used to achieve better efficiency.
+  static const char kHalfId2Sc_[kFullSplIdStart + 1];
+
+  static unsigned char char_flags_[];
+  static SpellingTrie* instance_;
+
+  // The spelling table
+  char *spelling_buf_;
+
+  // The size of longest spelling string, includes '\0' and an extra char to
+  // store score. For example, "zhuang" is the longgest item in Pinyin list,
+  // so spelling_size_ is 8.
+  // Structure: The string ended with '\0' + score char.
+  // An item with a lower score has a higher probability.
+  size_t spelling_size_;
+
+  // Number of full spelling ids.
+  size_t spelling_num_;
+
+  float score_amplifier_;
+  unsigned char average_score_;
+
+  // The Yunmu id list for the spelling ids (for half ids of Shengmu,
+  // the Yunmu id is 0).
+  // The length of the list is spelling_num_ + kFullSplIdStart,
+  // so that spl_ym_ids_[splid] is the Yunmu id of the splid.
+  uint8 *spl_ym_ids_;
+
+  // The Yunmu table.
+  // Each Yunmu will be assigned with Yunmu id from 1.
+  char *ym_buf_;
+  size_t ym_size_;  // The size of longest Yunmu string, '\0'included.
+  size_t ym_num_;
+
+  // The spelling string just queried
+  char *splstr_queried_;
+
+  // The spelling string just queried
+  char16 *splstr16_queried_;
+
+  // The root node of the spelling tree
+  SpellingNode* root_;
+
+  // If a none qwerty key such as a fnction key like ENTER is given, this node
+  // will be used to indicate that this is not a QWERTY node.
+  SpellingNode* dumb_node_;
+
+  // If a splitter key is pressed, this node will be used to indicate that this
+  // is a splitter key.
+  SpellingNode* splitter_node_;
+
+  // Used to get the first level sons.
+  SpellingNode* level1_sons_[kValidSplCharNum];
+
+  // The full spl_id range for specific half id.
+  // h2f means half to full.
+  // A half id can be a ShouZiMu id (id to represent the first char of a full
+  // spelling, including Shengmu and Yunmu), or id of zh/ch/sh.
+  // [1..kFullSplIdStart-1] is the arrange of half id.
+  uint16 h2f_start_[kFullSplIdStart];
+  uint16 h2f_num_[kFullSplIdStart];
+
+  // Map from full id to half id.
+  uint16 *f2h_;
+
+#ifdef ___BUILD_MODEL___
+  // How many node used to build the trie.
+  size_t node_num_;
+#endif
+
+  SpellingTrie();
+
+  void free_son_trie(SpellingNode* node);
+
+  // Construct a subtree using a subset of the spelling array (from
+  // item_star to item_end).
+  // Member spelliing_buf_ and spelling_size_ should be valid.
+  // parent is used to update its num_of_son and score.
+  SpellingNode* construct_spellings_subset(size_t item_start, size_t item_end,
+                                           size_t level, SpellingNode *parent);
+  bool build_f2h();
+
+  // The caller should guarantee ch >= 'A' && ch <= 'Z'
+  bool is_shengmu_char(char ch) const;
+
+  // The caller should guarantee ch >= 'A' && ch <= 'Z'
+  bool is_yunmu_char(char ch) const;
+
+#ifdef ___BUILD_MODEL___
+  // Given a spelling string, return its Yunmu string.
+  // The caller guaratees spl_str is valid.
+  const char* get_ym_str(const char *spl_str);
+
+  // Build the Yunmu list, and the mapping relation between the full ids and the
+  // Yunmu ids. This functin is called after the spelling trie is built.
+  bool build_ym_info();
+#endif
+
+  friend class SpellingParser;
+  friend class SmartSplParser;
+  friend class SmartSplParser2;
+
+ public:
+  ~SpellingTrie();
+
+  inline static bool is_valid_spl_char(char ch) {
+    return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
+  }
+
+  // The caller guarantees that the two chars are valid spelling chars.
+  inline static bool is_same_spl_char(char ch1, char ch2) {
+    return ch1 == ch2 || ch1 - ch2 == 'a' - 'A' || ch2 - ch1 == 'a' - 'A';
+  }
+
+  // Construct the tree from the input pinyin array
+  // The given string list should have been sorted.
+  // score_amplifier is used to convert a possibility value into score.
+  // average_score is the average_score of all spellings. The dumb node is
+  // assigned with this score.
+  bool construct(const char* spelling_arr, size_t item_size, size_t item_num,
+                 float score_amplifier, unsigned char average_score);
+
+  // Test if the given id is a valid spelling id.
+  // If function returns true, the given splid may be updated like this:
+  // When 'A' is not enabled in ShouZiMu mode, the parsing result for 'A' is
+  // first given as a half id 1, but because 'A' is a one-char Yunmu and
+  // it is a valid id, it needs to updated to its corresponding full id.
+  bool if_valid_id_update(uint16 *splid) const;
+
+  // Test if the given id is a half id.
+  bool is_half_id(uint16 splid) const;
+
+  bool is_full_id(uint16 splid) const;
+
+  // Test if the given id is a one-char Yunmu id (obviously, it is also a half
+  // id), such as 'A', 'E' and 'O'.
+  bool is_half_id_yunmu(uint16 splid) const;
+
+  // Test if this char is a ShouZiMu char. This ShouZiMu char may be not enabled.
+  // For Pinyin, only i/u/v is not a ShouZiMu char.
+  // The caller should guarantee that ch >= 'A' && ch <= 'Z'
+  bool is_szm_char(char ch) const;
+
+  // Test If this char is enabled in ShouZiMu mode.
+  // The caller should guarantee that ch >= 'A' && ch <= 'Z'
+  bool szm_is_enabled(char ch) const;
+
+  // Enable/disable Shengmus in ShouZiMu mode(using the first char of a spelling
+  // to input).
+  void szm_enable_shm(bool enable);
+
+  // Enable/disable Yunmus in ShouZiMu mode.
+  void szm_enable_ym(bool enable);
+
+  // Test if this char is enabled in ShouZiMu mode.
+  // The caller should guarantee ch >= 'A' && ch <= 'Z'
+  bool is_szm_enabled(char ch) const;
+
+  // Return the number of full ids for the given half id.
+  uint16 half2full_num(uint16 half_id) const;
+
+  // Return the number of full ids for the given half id, and fill spl_id_start
+  // to return the first full id.
+  uint16 half_to_full(uint16 half_id, uint16 *spl_id_start) const;
+
+  // Return the corresponding half id for the given full id.
+  // Not frequently used, low efficient.
+  // Return 0 if fails.
+  uint16 full_to_half(uint16 full_id) const;
+
+  // To test whether a half id is compatible with a full id.
+  // Generally, when half_id == full_to_half(full_id), return true.
+  // But for "Zh, Ch, Sh", if fussy mode is on, half id for 'Z' is compatible
+  // with a full id like "Zhe". (Fussy mode is not ready).
+  bool half_full_compatible(uint16 half_id, uint16 full_id) const;
+
+  static const SpellingTrie* get_cpinstance();
+
+  static SpellingTrie& get_instance();
+
+  // Save to the file stream
+  bool save_spl_trie(FILE *fp);
+
+  // Load from the file stream
+  bool load_spl_trie(FILE *fp);
+
+  // Get the number of spellings
+  size_t get_spelling_num();
+
+  // Return the Yunmu id for the given Yunmu string.
+  // If the string is not valid, return 0;
+  uint8 get_ym_id(const char* ym_str);
+
+  // Get the readonly Pinyin string for a given spelling id
+  const char* get_spelling_str(uint16 splid);
+
+  // Get the readonly Pinyin string for a given spelling id
+  const char16* get_spelling_str16(uint16 splid);
+
+  // Get Pinyin string for a given spelling id. Return the length of the
+  // string, and fill-in '\0' at the end.
+  size_t get_spelling_str16(uint16 splid, char16 *splstr16,
+                            size_t splstr16_len);
+};
+}
+
+#endif  // PINYINIME_INCLUDE_SPELLINGTRIE_H__
diff --git a/PinyinIME/jni/include/splparser.h b/PinyinIME/jni/include/splparser.h
new file mode 100644
index 0000000..d783bd7
--- /dev/null
+++ b/PinyinIME/jni/include/splparser.h
@@ -0,0 +1,96 @@
+/*
+ * 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 PINYINIME_INCLUDE_SPLPARSER_H__
+#define PINYINIME_INCLUDE_SPLPARSER_H__
+
+#include "./dictdef.h"
+#include "./spellingtrie.h"
+
+namespace ime_pinyin {
+
+class SpellingParser {
+ protected:
+  const SpellingTrie *spl_trie_;
+
+ public:
+  SpellingParser();
+
+  // Given a string, parse it into a spelling id stream.
+  // If the whole string are sucessfully parsed, last_is_pre will be true;
+  // if the whole string is not fullly parsed, last_is_pre will return whether
+  // the last part of the string is a prefix of a full spelling string. For
+  // example, given string "zhengzhon", "zhon" is not a valid speling, but it is
+  // the prefix of "zhong".
+  //
+  // If splstr starts with a character not in ['a'-z'] (it is a split char),
+  // return 0.
+  // Split char can only appear in the middle of the string or at the end.
+  uint16 splstr_to_idxs(const char *splstr, uint16 str_len, uint16 splidx[],
+                        uint16 start_pos[], uint16 max_size, bool &last_is_pre);
+
+  // Similar to splstr_to_idxs(), the only difference is that splstr_to_idxs()
+  // convert single-character Yunmus into half ids, while this function converts
+  // them into full ids.
+  uint16 splstr_to_idxs_f(const char *splstr, uint16 str_len, uint16 splidx[],
+          uint16 start_pos[], uint16 max_size, bool &last_is_pre);
+
+  // Similar to splstr_to_idxs(), the only difference is that this function
+  // uses char16 instead of char8.
+  uint16 splstr16_to_idxs(const char16 *splstr, uint16 str_len, uint16 splidx[],
+                        uint16 start_pos[], uint16 max_size, bool &last_is_pre);
+
+  // Similar to splstr_to_idxs_f(), the only difference is that this function
+  // uses char16 instead of char8.
+  uint16 splstr16_to_idxs_f(const char16 *splstr16, uint16 str_len,
+                            uint16 splidx[], uint16 start_pos[],
+                            uint16 max_size, bool &last_is_pre);
+
+  // If the given string is a spelling, return the id, others, return 0.
+  // If the give string is a single char Yunmus like "A", and the char is
+  // enabled in ShouZiMu mode, the returned spelling id will be a half id.
+  // When the returned spelling id is a half id, *is_pre returns whether it
+  // is a prefix of a full spelling string.
+  uint16 get_splid_by_str(const char *splstr, uint16 str_len, bool *is_pre);
+
+  // If the given string is a spelling, return the id, others, return 0.
+  // If the give string is a single char Yunmus like "a", no matter the char
+  // is enabled in ShouZiMu mode or not, the returned spelling id will be
+  // a full id.
+  // When the returned spelling id is a half id, *p_is_pre returns whether it
+  // is a prefix of a full spelling string.
+  uint16 get_splid_by_str_f(const char *splstr, uint16 str_len, bool *is_pre);
+
+  // Splitter chars are not included.
+  bool is_valid_to_parse(char ch);
+
+  // When auto-correction is not enabled, get_splid_by_str() will be called to
+  // return the single result. When auto-correction is enabled, this function
+  // will be called to get the results. Auto-correction is not ready.
+  // full_id_num returns number of full spelling ids.
+  // is_pre returns whether the given string is the prefix of a full spelling
+  // string.
+  // If splstr starts with a character not in [a-zA-Z] (it is a split char),
+  // return 0.
+  // Split char can only appear in the middle of the string or at the end.
+  // The caller should guarantee NULL != splstr && str_len > 0 && NULL != splidx
+  uint16 get_splids_parallel(const char *splstr, uint16 str_len,
+                             uint16 splidx[], uint16 max_size,
+                             uint16 &full_id_num, bool &is_pre);
+};
+}
+
+#endif  // PINYINIME_INCLUDE_SPLPARSER_H__
diff --git a/PinyinIME/jni/include/sync.h b/PinyinIME/jni/include/sync.h
new file mode 100644
index 0000000..bf42d1f
--- /dev/null
+++ b/PinyinIME/jni/include/sync.h
@@ -0,0 +1,85 @@
+/*
+ * 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 PINYINIME_INCLUDE_SYNC_H__
+#define PINYINIME_INCLUDE_SYNC_H__
+
+#define ___SYNC_ENABLED___
+
+#ifdef ___SYNC_ENABLED___
+
+#include "userdict.h"
+
+namespace ime_pinyin {
+
+// Class for user dictionary synchronization
+// This class is not thread safe
+// Normal invoking flow will be
+//   begin() ->
+//   put_lemmas() x N ->
+//   {
+//     get_lemmas() ->
+//     [ get_last_got_count() ] ->
+//     clear_last_got() ->
+//   } x N ->
+//   finish()
+class Sync {
+ public:
+  Sync();
+  ~Sync();
+
+  static const int kUserDictMaxLemmaCount = 5000;
+  static const int kUserDictMaxLemmaSize = 200000;
+  static const int kUserDictRatio = 20;
+
+  bool begin(const char * filename);
+
+  // Merge lemmas downloaded from sync server into local dictionary
+  // lemmas, lemmas string encoded in UTF16LE
+  // len, length of lemmas string
+  // Return how many lemmas merged successfully
+  int put_lemmas(char16 * lemmas, int len);
+
+  // Get local new user lemmas into UTF16LE string
+  // str, buffer ptr to store new user lemmas
+  // size, size of buffer
+  // Return length of returned buffer in measure of UTF16LE
+  int get_lemmas(char16 * str, int size);
+
+  // Return lemmas count in last get_lemmas()
+  int get_last_got_count();
+
+  // Return total lemmas count need get_lemmas()
+  int get_total_count();
+
+  // Clear lemmas got by recent get_lemmas()
+  void clear_last_got();
+
+  void finish();
+
+  int get_capacity();
+
+ private:
+  UserDict * userdict_;
+  char * dictfile_;
+  int last_count_;
+};
+
+}
+
+#endif
+
+#endif  // PINYINIME_INCLUDE_SYNC_H__
diff --git a/PinyinIME/jni/include/userdict.h b/PinyinIME/jni/include/userdict.h
new file mode 100644
index 0000000..02da218
--- /dev/null
+++ b/PinyinIME/jni/include/userdict.h
@@ -0,0 +1,428 @@
+/*
+ * 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 PINYINIME_INCLUDE_USERDICT_H__
+#define PINYINIME_INCLUDE_USERDICT_H__
+
+#define ___CACHE_ENABLED___
+#define ___SYNC_ENABLED___
+#define ___PREDICT_ENABLED___
+
+// Debug performance for operations
+// #define ___DEBUG_PERF___
+
+#include <pthread.h>
+#include "atomdictbase.h"
+
+namespace ime_pinyin {
+
+class UserDict : public AtomDictBase {
+ public:
+  UserDict();
+  ~UserDict();
+
+  bool load_dict(const char *file_name, LemmaIdType start_id,
+                 LemmaIdType end_id);
+
+  bool close_dict();
+
+  size_t number_of_lemmas();
+
+  void reset_milestones(uint16 from_step, MileStoneHandle from_handle);
+
+  MileStoneHandle extend_dict(MileStoneHandle from_handle,
+                              const DictExtPara *dep, LmaPsbItem *lpi_items,
+                              size_t lpi_max, size_t *lpi_num);
+
+  size_t get_lpis(const uint16 *splid_str, uint16 splid_str_len,
+                  LmaPsbItem *lpi_items, size_t lpi_max);
+
+  uint16 get_lemma_str(LemmaIdType id_lemma, char16* str_buf,
+                       uint16 str_max);
+
+  uint16 get_lemma_splids(LemmaIdType id_lemma, uint16 *splids,
+                          uint16 splids_max, bool arg_valid);
+
+  size_t predict(const char16 last_hzs[], uint16 hzs_len,
+                 NPredictItem *npre_items, size_t npre_max,
+                 size_t b4_used);
+
+  // Full spelling ids are required
+  LemmaIdType put_lemma(char16 lemma_str[], uint16 splids[],
+                        uint16 lemma_len, uint16 count);
+
+  LemmaIdType update_lemma(LemmaIdType lemma_id, int16 delta_count,
+                           bool selected);
+
+  LemmaIdType get_lemma_id(char16 lemma_str[], uint16 splids[],
+                           uint16 lemma_len);
+
+  LmaScoreType get_lemma_score(LemmaIdType lemma_id);
+
+  LmaScoreType get_lemma_score(char16 lemma_str[], uint16 splids[],
+                        uint16 lemma_len);
+
+  bool remove_lemma(LemmaIdType lemma_id);
+
+  size_t get_total_lemma_count();
+  void set_total_lemma_count_of_others(size_t count);
+
+  void flush_cache();
+
+  void set_limit(uint32 max_lemma_count, uint32 max_lemma_size,
+                 uint32 reclaim_ratio);
+
+  void reclaim();
+
+  void defragment();
+
+#ifdef ___SYNC_ENABLED___
+  void clear_sync_lemmas(unsigned int start, unsigned int end);
+
+  int get_sync_count();
+
+  LemmaIdType put_lemma_no_sync(char16 lemma_str[], uint16 splids[],
+                        uint16 lemma_len, uint16 count, uint64 lmt);
+   /**
+    * Add lemmas encoded in UTF-16LE into dictionary without adding sync flag.
+    *
+    * @param lemmas in format of 'wo men,WM,0.32;da jia,DJ,0.12'
+    * @param len length of lemmas string in UTF-16LE
+    * @return newly added lemma count
+    */
+  int put_lemmas_no_sync_from_utf16le_string(char16 * lemmas, int len);
+
+  /**
+   * Get lemmas need sync to a UTF-16LE string of above format.
+   * Note: input buffer (str) must not be too small. If str is too small to 
+   *       contain single one lemma, there might be a dead loop.
+   *
+   * @param str buffer to write lemmas
+   * @param size buffer size in UTF-16LE
+   * @param count output value of lemma returned
+   * @return UTF-16LE string length
+   */
+  int get_sync_lemmas_in_utf16le_string_from_beginning(
+      char16 * str, int size, int * count);
+
+#endif
+
+  struct UserDictStat {
+    uint32 version;
+    const char * file_name;
+    struct timeval load_time;
+    struct timeval last_update;
+    uint32 disk_size;
+    uint32 lemma_count;
+    uint32 lemma_size;
+    uint32 delete_count;
+    uint32 delete_size;
+#ifdef ___SYNC_ENABLED___
+    uint32 sync_count;
+#endif
+    uint32 reclaim_ratio;
+    uint32 limit_lemma_count;
+    uint32 limit_lemma_size;
+  };
+
+  bool state(UserDictStat * stat);
+
+ private:
+  uint32 total_other_nfreq_;
+  struct timeval load_time_;
+  LemmaIdType start_id_;
+  uint32 version_;
+  uint8 * lemmas_;
+
+  // In-Memory-Only flag for each lemma
+  static const uint8 kUserDictLemmaFlagRemove = 1;
+  // Inuse lemmas' offset
+  uint32 * offsets_;
+  // Highest bit in offset tells whether corresponding lemma is removed
+  static const uint32 kUserDictOffsetFlagRemove = (1 << 31);
+  // Maximum possible for the offset
+  static const uint32 kUserDictOffsetMask = ~(kUserDictOffsetFlagRemove);
+  // Bit width for last modified time, from 1 to 16
+  static const uint32 kUserDictLMTBitWidth = 16;
+  // Granularity for last modified time in second
+  static const uint32 kUserDictLMTGranularity = 60 * 60 * 24 * 7;
+  // Maximum frequency count
+  static const uint16 kUserDictMaxFrequency = 0xFFFF;
+
+#define COARSE_UTC(year, month, day, hour, minute, second) \
+  ( \
+    (year - 1970) * 365 * 24 * 60 * 60 + \
+    (month - 1) * 30 * 24 * 60 * 60 + \
+    (day - 1) * 24 * 60 * 60 + \
+    (hour - 0) * 60 * 60 + \
+    (minute - 0) * 60 + \
+    (second - 0) \
+  )
+  static const uint64 kUserDictLMTSince = COARSE_UTC(2009, 1, 1, 0, 0, 0);
+
+  // Correspond to offsets_
+  uint32 * scores_;
+  // Following two fields are only valid in memory
+  uint32 * ids_;
+#ifdef ___PREDICT_ENABLED___
+  uint32 * predicts_;
+#endif
+#ifdef ___SYNC_ENABLED___
+  uint32 * syncs_;
+  size_t sync_count_size_;
+#endif
+  uint32 * offsets_by_id_;
+
+  size_t lemma_count_left_;
+  size_t lemma_size_left_;
+
+  const char * dict_file_;
+
+  // Be sure size is 4xN
+  struct UserDictInfo {
+    // When limitation reached, how much percentage will be reclaimed (1 ~ 100)
+    uint32 reclaim_ratio;
+    // maximum lemma count, 0 means no limitation
+    uint32 limit_lemma_count;
+    // Maximum lemma size, it's different from
+    // whole disk file size or in-mem dict size
+    // 0 means no limitation
+    uint32 limit_lemma_size;
+    // Total lemma count including deleted and inuse
+    // Also indicate offsets_ size
+    uint32 lemma_count;
+    // Total size of lemmas including used and freed
+    uint32 lemma_size;
+    // Freed lemma count
+    uint32 free_count;
+    // Freed lemma size in byte
+    uint32 free_size;
+#ifdef ___SYNC_ENABLED___
+    uint32 sync_count;
+#endif
+    int32 total_nfreq;
+  } dict_info_;
+
+  static const uint32 kUserDictVersion = 0x0ABCDEF0;
+
+  static const uint32 kUserDictPreAlloc = 32;
+  static const uint32 kUserDictAverageNchar = 8;
+
+  enum UserDictState {
+    // Keep in order
+    USER_DICT_NONE = 0,
+    USER_DICT_SYNC,
+#ifdef ___SYNC_ENABLED___
+    USER_DICT_SYNC_DIRTY,
+#endif
+    USER_DICT_SCORE_DIRTY,
+    USER_DICT_OFFSET_DIRTY,
+    USER_DICT_LEMMA_DIRTY,
+
+    USER_DICT_DEFRAGMENTED,
+  } state_;
+
+  struct UserDictSearchable {
+    uint16 splids_len;
+    uint16 splid_start[kMaxLemmaSize];
+    uint16 splid_count[kMaxLemmaSize];
+    // Compact inital letters for both FuzzyCompareSpellId and cache system
+    uint32 signature[kMaxLemmaSize / 4];
+  };
+
+#ifdef ___CACHE_ENABLED___
+  enum UserDictCacheType {
+    USER_DICT_CACHE,
+    USER_DICT_MISS_CACHE,
+  };
+
+  static const int kUserDictCacheSize = 4;
+  static const int kUserDictMissCacheSize = kMaxLemmaSize - 1;
+
+  struct UserDictMissCache {
+    uint32 signatures[kUserDictMissCacheSize][kMaxLemmaSize / 4];
+    uint16 head, tail;
+  } miss_caches_[kMaxLemmaSize];
+
+  struct UserDictCache {
+    uint32 signatures[kUserDictCacheSize][kMaxLemmaSize / 4];
+    uint32 offsets[kUserDictCacheSize];
+    uint32 lengths[kUserDictCacheSize];
+    // Ring buffer
+    uint16 head, tail;
+  } caches_[kMaxLemmaSize];
+
+  void cache_init();
+
+  void cache_push(UserDictCacheType type,
+                 UserDictSearchable *searchable,
+                 uint32 offset, uint32 length);
+
+  bool cache_hit(UserDictSearchable *searchable,
+                 uint32 *offset, uint32 *length);
+
+  bool load_cache(UserDictSearchable *searchable,
+                  uint32 *offset, uint32 *length);
+
+  void save_cache(UserDictSearchable *searchable,
+                  uint32 offset, uint32 length);
+
+  void reset_cache();
+
+  bool load_miss_cache(UserDictSearchable *searchable);
+
+  void save_miss_cache(UserDictSearchable *searchable);
+
+  void reset_miss_cache();
+#endif
+
+  LmaScoreType translate_score(int f);
+
+  int extract_score_freq(int raw_score);
+
+  uint64 extract_score_lmt(int raw_score);
+
+  inline int build_score(uint64 lmt, int freq);
+
+  inline int64 utf16le_atoll(uint16 *s, int len);
+
+  inline int utf16le_lltoa(int64 v, uint16 *s, int size);
+
+  LemmaIdType _put_lemma(char16 lemma_str[], uint16 splids[],
+                        uint16 lemma_len, uint16 count, uint64 lmt);
+
+  size_t _get_lpis(const uint16 *splid_str, uint16 splid_str_len,
+                   LmaPsbItem *lpi_items, size_t lpi_max, bool * need_extend);
+
+  int _get_lemma_score(char16 lemma_str[], uint16 splids[], uint16 lemma_len);
+
+  int _get_lemma_score(LemmaIdType lemma_id);
+
+  int is_fuzzy_prefix_spell_id(const uint16 * id1, uint16 len1,
+                               const UserDictSearchable *searchable);
+
+  bool is_prefix_spell_id(const uint16 * fullids,
+                          uint16 fulllen, const UserDictSearchable *searchable);
+
+  uint32 get_dict_file_size(UserDictInfo * info);
+
+  bool reset(const char *file);
+
+  bool validate(const char *file);
+
+  bool load(const char *file, LemmaIdType start_id);
+
+  bool is_valid_state();
+
+  bool is_valid_lemma_id(LemmaIdType id);
+
+  LemmaIdType get_max_lemma_id();
+
+  void set_lemma_flag(uint32 offset, uint8 flag);
+
+  char get_lemma_flag(uint32 offset);
+
+  char get_lemma_nchar(uint32 offset);
+
+  uint16 * get_lemma_spell_ids(uint32 offset);
+
+  uint16 * get_lemma_word(uint32 offset);
+
+  // Prepare searchable to fasten locate process
+  void prepare_locate(UserDictSearchable *searchable,
+                      const uint16 * splids, uint16 len);
+
+  // Compare initial letters only
+  int32 fuzzy_compare_spell_id(const uint16 * id1, uint16 len1,
+                               const UserDictSearchable *searchable);
+
+  // Compare exactly two spell ids
+  // First argument must be a full id spell id
+  bool equal_spell_id(const uint16 * fullids,
+                      uint16 fulllen, const UserDictSearchable *searchable);
+
+  // Find first item by initial letters
+  int32 locate_first_in_offsets(const UserDictSearchable *searchable);
+
+  LemmaIdType append_a_lemma(char16 lemma_str[], uint16 splids[],
+                           uint16 lemma_len, uint16 count, uint64 lmt);
+
+  // Check if a lemma is in dictionary
+  int32 locate_in_offsets(char16 lemma_str[],
+                          uint16 splid_str[], uint16 lemma_len);
+
+  bool remove_lemma_by_offset_index(int offset_index);
+#ifdef ___PREDICT_ENABLED___
+  uint32 locate_where_to_insert_in_predicts(const uint16 * words,
+                                            int lemma_len);
+
+  int32 locate_first_in_predicts(const uint16 * words, int lemma_len);
+
+  void remove_lemma_from_predict_list(uint32 offset);
+#endif
+#ifdef ___SYNC_ENABLED___
+  void queue_lemma_for_sync(LemmaIdType id);
+
+  void remove_lemma_from_sync_list(uint32 offset);
+
+  void write_back_sync(int fd);
+#endif
+  void write_back_score(int fd);
+  void write_back_offset(int fd);
+  void write_back_lemma(int fd);
+  void write_back_all(int fd);
+  void write_back();
+
+  struct UserDictScoreOffsetPair {
+    int score;
+    uint32 offset_index;
+  };
+
+  inline void swap(UserDictScoreOffsetPair * sop, int i, int j);
+
+  void shift_down(UserDictScoreOffsetPair * sop, int i, int n);
+
+  // On-disk format for each lemma
+  // +-------------+
+  // | Version (4) |
+  // +-------------+
+  // +-----------+-----------+--------------------+-------------------+
+  // | Spare (1) | Nchar (1) | Splids (2 x Nchar) | Lemma (2 x Nchar) |
+  // +-----------+-----------+--------------------+-------------------+
+  // ...
+  // +-----------------------+     +-------------+      <---Offset of offset
+  // | Offset1 by_splids (4) | ... | OffsetN (4) |
+  // +-----------------------+     +-------------+
+#ifdef ___PREDICT_ENABLED___
+  // +----------------------+     +-------------+
+  // | Offset1 by_lemma (4) | ... | OffsetN (4) |
+  // +----------------------+     +-------------+
+#endif
+  // +------------+     +------------+
+  // | Score1 (4) | ... | ScoreN (4) |
+  // +------------+     +------------+
+#ifdef ___SYNC_ENABLED___
+  // +-------------+     +-------------+
+  // | NewAdd1 (4) | ... | NewAddN (4) |
+  // +-------------+     +-------------+
+#endif
+  // +----------------+
+  // | Dict Info (4x) |
+  // +----------------+
+};
+}
+
+#endif
diff --git a/PinyinIME/jni/include/utf16char.h b/PinyinIME/jni/include/utf16char.h
new file mode 100644
index 0000000..7e957db
--- /dev/null
+++ b/PinyinIME/jni/include/utf16char.h
@@ -0,0 +1,56 @@
+/*
+ * 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 PINYINIME_INCLUDE_UTF16CHAR_H__
+#define PINYINIME_INCLUDE_UTF16CHAR_H__
+
+#include <stdlib.h>
+
+namespace ime_pinyin {
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  typedef unsigned short char16;
+
+  // Get a token from utf16_str,
+  // Returned pointer is a '\0'-terminated utf16 string, or NULL
+  // *utf16_str_next returns the next part of the string for further tokenizing
+  char16* utf16_strtok(char16 *utf16_str, size_t *token_size,
+                       char16 **utf16_str_next);
+
+  int utf16_atoi(const char16 *utf16_str);
+
+  float utf16_atof(const char16 *utf16_str);
+
+  size_t utf16_strlen(const char16 *utf16_str);
+
+  int utf16_strcmp(const char16 *str1, const char16 *str2);
+  int utf16_strncmp(const char16 *str1, const char16 *str2, size_t size);
+
+  char16* utf16_strcpy(char16 *dst, const char16 *src);
+  char16* utf16_strncpy(char16 *dst, const char16 *src, size_t size);
+
+
+  char* utf16_strcpy_tochar(char *dst, const char16 *src);
+
+#ifdef __cplusplus
+}
+#endif
+}
+
+#endif  // PINYINIME_INCLUDE_UTF16CHAR_H__
diff --git a/PinyinIME/jni/include/utf16reader.h b/PinyinIME/jni/include/utf16reader.h
new file mode 100644
index 0000000..b6d6719
--- /dev/null
+++ b/PinyinIME/jni/include/utf16reader.h
@@ -0,0 +1,48 @@
+/*
+ * 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 PINYINIME_INCLUDE_UTF16READER_H__
+#define PINYINIME_INCLUDE_UTF16READER_H__
+
+#include <stdio.h>
+#include "./utf16char.h"
+
+namespace ime_pinyin {
+
+class Utf16Reader {
+ private:
+  FILE *fp_;
+  char16 *buffer_;
+  size_t buffer_total_len_;
+  size_t buffer_next_pos_;
+
+  // Always less than buffer_total_len_ - buffer_next_pos_
+  size_t buffer_valid_len_;
+
+ public:
+  Utf16Reader();
+  ~Utf16Reader();
+
+  // filename is the name of the file to open.
+  // buffer_len specifies how long buffer should be allocated to speed up the
+  // future reading
+  bool open(const char* filename, size_t buffer_len);
+  char16* readline(char16* read_buf, size_t max_len);
+  bool close();
+};
+}
+
+#endif  // PINYINIME_INCLUDE_UTF16READER_H__
diff --git a/PinyinIME/jni/share/dictbuilder.cpp b/PinyinIME/jni/share/dictbuilder.cpp
new file mode 100644
index 0000000..b2e989c
--- /dev/null
+++ b/PinyinIME/jni/share/dictbuilder.cpp
@@ -0,0 +1,1080 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../include/dictbuilder.h"
+#include "../include/dicttrie.h"
+#include "../include/mystdlib.h"
+#include "../include/ngram.h"
+#include "../include/searchutility.h"
+#include "../include/spellingtable.h"
+#include "../include/spellingtrie.h"
+#include "../include/splparser.h"
+#include "../include/utf16reader.h"
+
+namespace ime_pinyin {
+
+#ifdef ___BUILD_MODEL___
+
+static const size_t kReadBufLen = 512;
+static const size_t kSplTableHashLen = 2000;
+
+// Compare a SingleCharItem, first by Hanzis, then by spelling ids, then by
+// frequencies.
+int cmp_scis_hz_splid_freq(const void* p1, const void* p2) {
+  const SingleCharItem *s1, *s2;
+  s1 = static_cast<const SingleCharItem*>(p1);
+  s2 = static_cast<const SingleCharItem*>(p2);
+
+  if (s1->hz < s2->hz)
+    return -1;
+  if (s1->hz > s2->hz)
+    return 1;
+
+  if (s1->splid.half_splid < s2->splid.half_splid)
+    return -1;
+  if (s1->splid.half_splid > s2->splid.half_splid)
+    return 1;
+
+  if (s1->splid.full_splid < s2->splid.full_splid)
+    return -1;
+  if (s1->splid.full_splid > s2->splid.full_splid)
+    return 1;
+
+  if (s1->freq > s2->freq)
+    return -1;
+  if (s1->freq < s2->freq)
+    return 1;
+  return 0;
+}
+
+int cmp_scis_hz_splid(const void* p1, const void* p2) {
+  const SingleCharItem *s1, *s2;
+  s1 = static_cast<const SingleCharItem*>(p1);
+  s2 = static_cast<const SingleCharItem*>(p2);
+
+  if (s1->hz < s2->hz)
+    return -1;
+  if (s1->hz > s2->hz)
+    return 1;
+
+  if (s1->splid.half_splid < s2->splid.half_splid)
+    return -1;
+  if (s1->splid.half_splid > s2->splid.half_splid)
+    return 1;
+
+  if (s1->splid.full_splid < s2->splid.full_splid)
+    return -1;
+  if (s1->splid.full_splid > s2->splid.full_splid)
+    return 1;
+
+  return 0;
+}
+
+int cmp_lemma_entry_hzs(const void* p1, const void* p2) {
+  size_t size1 = utf16_strlen(((const LemmaEntry*)p1)->hanzi_str);
+  size_t size2 = utf16_strlen(((const LemmaEntry*)p2)->hanzi_str);
+  if (size1 < size2)
+    return -1;
+  else if (size1 > size2)
+    return 1;
+
+  return utf16_strcmp(((const LemmaEntry*)p1)->hanzi_str,
+                      ((const LemmaEntry*)p2)->hanzi_str);
+}
+
+int compare_char16(const void* p1, const void* p2) {
+  if (*((const char16*)p1) < *((const char16*)p2))
+    return -1;
+  if (*((const char16*)p1) > *((const char16*)p2))
+    return 1;
+  return 0;
+}
+
+int compare_py(const void* p1, const void* p2) {
+  int ret = utf16_strcmp(((const LemmaEntry*)p1)->spl_idx_arr,
+                         ((const LemmaEntry*)p2)->spl_idx_arr);
+
+  if (0 != ret)
+    return ret;
+
+  return static_cast<int>(((const LemmaEntry*)p2)->freq) -
+         static_cast<int>(((const LemmaEntry*)p1)->freq);
+}
+
+// First hanzi, if the same, then Pinyin
+int cmp_lemma_entry_hzspys(const void* p1, const void* p2) {
+  size_t size1 = utf16_strlen(((const LemmaEntry*)p1)->hanzi_str);
+  size_t size2 = utf16_strlen(((const LemmaEntry*)p2)->hanzi_str);
+  if (size1 < size2)
+    return -1;
+  else if (size1 > size2)
+    return 1;
+  int ret = utf16_strcmp(((const LemmaEntry*)p1)->hanzi_str,
+                         ((const LemmaEntry*)p2)->hanzi_str);
+
+  if (0 != ret)
+    return ret;
+
+  ret = utf16_strcmp(((const LemmaEntry*)p1)->spl_idx_arr,
+                     ((const LemmaEntry*)p2)->spl_idx_arr);
+  return ret;
+}
+
+int compare_splid2(const void* p1, const void* p2) {
+  int ret = utf16_strcmp(((const LemmaEntry*)p1)->spl_idx_arr,
+                         ((const LemmaEntry*)p2)->spl_idx_arr);
+  return ret;
+}
+
+DictBuilder::DictBuilder() {
+  lemma_arr_ = NULL;
+  lemma_num_ = 0;
+
+  scis_ = NULL;
+  scis_num_ = 0;
+
+  lma_nodes_le0_ = NULL;
+  lma_nodes_ge1_ = NULL;
+
+  lma_nds_used_num_le0_ = 0;
+  lma_nds_used_num_ge1_ = 0;
+
+  homo_idx_buf_ = NULL;
+  homo_idx_num_eq1_ = 0;
+  homo_idx_num_gt1_ = 0;
+
+  top_lmas_ = NULL;
+  top_lmas_num_ = 0;
+
+  spl_table_ = NULL;
+  spl_parser_ = NULL;
+}
+
+DictBuilder::~DictBuilder() {
+  free_resource();
+}
+
+bool DictBuilder::alloc_resource(size_t lma_num) {
+  if (0 == lma_num)
+    return false;
+
+  free_resource();
+
+  lemma_num_ = lma_num;
+  lemma_arr_ = new LemmaEntry[lemma_num_];
+
+  top_lmas_num_ = 0;
+  top_lmas_ = new LemmaEntry[kTopScoreLemmaNum];
+
+  // New the scis_ buffer to the possible maximum size.
+  scis_num_ = lemma_num_ * kMaxLemmaSize;
+  scis_ = new SingleCharItem[scis_num_];
+
+  // The root and first level nodes is less than kMaxSpellingNum + 1
+  lma_nds_used_num_le0_ = 0;
+  lma_nodes_le0_ = new LmaNodeLE0[kMaxSpellingNum + 1];
+
+  // Other nodes is less than lemma_num
+  lma_nds_used_num_ge1_ = 0;
+  lma_nodes_ge1_ = new LmaNodeGE1[lemma_num_];
+
+  homo_idx_buf_ = new LemmaIdType[lemma_num_];
+  spl_table_ = new SpellingTable();
+  spl_parser_ = new SpellingParser();
+
+  if (NULL == lemma_arr_ || NULL == top_lmas_ ||
+      NULL == scis_ || NULL == spl_table_ ||
+      NULL == spl_parser_ || NULL == lma_nodes_le0_ ||
+      NULL == lma_nodes_ge1_ || NULL == homo_idx_buf_) {
+    free_resource();
+    return false;
+  }
+
+  memset(lemma_arr_, 0, sizeof(LemmaEntry) * lemma_num_);
+  memset(scis_, 0, sizeof(SingleCharItem) * scis_num_);
+  memset(lma_nodes_le0_, 0, sizeof(LmaNodeLE0) * (kMaxSpellingNum + 1));
+  memset(lma_nodes_ge1_, 0, sizeof(LmaNodeGE1) * lemma_num_);
+  memset(homo_idx_buf_, 0, sizeof(LemmaIdType) * lemma_num_);
+  spl_table_->init_table(kMaxPinyinSize, kSplTableHashLen, true);
+
+  return true;
+}
+
+char16* DictBuilder::read_valid_hanzis(const char *fn_validhzs, size_t *num) {
+  if (NULL == fn_validhzs || NULL == num)
+    return NULL;
+
+  *num = 0;
+  FILE *fp = fopen(fn_validhzs, "rb");
+  if (NULL == fp)
+    return NULL;
+
+  char16 utf16header;
+  if (fread(&utf16header, sizeof(char16), 1, fp) != 1 ||
+      0xfeff != utf16header) {
+    fclose(fp);
+    return NULL;
+  }
+
+  fseek(fp, 0, SEEK_END);
+  *num = ftell(fp) / sizeof(char16);
+  assert(*num >= 1);
+  *num -= 1;
+
+  char16 *hzs = new char16[*num];
+  if (NULL == hzs) {
+    fclose(fp);
+    return NULL;
+  }
+
+  fseek(fp, 2, SEEK_SET);
+
+  if (fread(hzs, sizeof(char16), *num, fp) != *num) {
+    fclose(fp);
+    delete [] hzs;
+    return NULL;
+  }
+  fclose(fp);
+
+  myqsort(hzs, *num, sizeof(char16), compare_char16);
+  return hzs;
+}
+
+bool DictBuilder::hz_in_hanzis_list(const char16 *hzs, size_t hzs_len,
+                                    char16 hz) {
+  if (NULL == hzs)
+    return false;
+
+  char16 *found;
+  found = static_cast<char16*>(
+      mybsearch(&hz, hzs, hzs_len, sizeof(char16), compare_char16));
+  if (NULL == found)
+    return false;
+
+  assert(*found == hz);
+  return true;
+}
+
+// The caller makes sure that the parameters are valid.
+bool DictBuilder::str_in_hanzis_list(const char16 *hzs, size_t hzs_len,
+                                     const char16 *str, size_t str_len) {
+  if (NULL == hzs || NULL == str)
+    return false;
+
+  for (size_t pos = 0; pos < str_len; pos++) {
+    if (!hz_in_hanzis_list(hzs, hzs_len, str[pos]))
+      return false;
+  }
+  return true;
+}
+
+void DictBuilder::get_top_lemmas() {
+  top_lmas_num_ = 0;
+  if (NULL == lemma_arr_)
+    return;
+
+  for (size_t pos = 0; pos < lemma_num_; pos++) {
+    if (0 == top_lmas_num_) {
+      top_lmas_[0] = lemma_arr_[pos];
+      top_lmas_num_ = 1;
+      continue;
+    }
+
+    if (lemma_arr_[pos].freq > top_lmas_[top_lmas_num_ - 1].freq) {
+      if (kTopScoreLemmaNum > top_lmas_num_)
+        top_lmas_num_ += 1;
+
+      size_t move_pos;
+      for (move_pos = top_lmas_num_ - 1; move_pos > 0; move_pos--) {
+        top_lmas_[move_pos] = top_lmas_[move_pos - 1];
+        if (0 == move_pos - 1 ||
+            (move_pos - 1 > 0 &&
+             top_lmas_[move_pos - 2].freq > lemma_arr_[pos].freq)) {
+          break;
+        }
+      }
+      assert(move_pos > 0);
+      top_lmas_[move_pos - 1] = lemma_arr_[pos];
+    } else if (kTopScoreLemmaNum > top_lmas_num_) {
+      top_lmas_[top_lmas_num_] = lemma_arr_[pos];
+      top_lmas_num_ += 1;
+    }
+  }
+
+  if (kPrintDebug0) {
+    printf("\n------Top Lemmas------------------\n");
+    for (size_t pos = 0; pos < top_lmas_num_; pos++) {
+      printf("--%d, idx:%06d, score:%.5f\n", pos, top_lmas_[pos].idx_by_hz,
+             top_lmas_[pos].freq);
+    }
+  }
+}
+
+void DictBuilder::free_resource() {
+  if (NULL != lemma_arr_)
+    delete [] lemma_arr_;
+
+  if (NULL != scis_)
+    delete [] scis_;
+
+  if (NULL != lma_nodes_le0_)
+    delete [] lma_nodes_le0_;
+
+  if (NULL != lma_nodes_ge1_)
+    delete [] lma_nodes_ge1_;
+
+  if (NULL != homo_idx_buf_)
+    delete [] homo_idx_buf_;
+
+  if (NULL != spl_table_)
+    delete spl_table_;
+
+  if (NULL != spl_parser_)
+    delete spl_parser_;
+
+  lemma_arr_ = NULL;
+  scis_ = NULL;
+  lma_nodes_le0_ = NULL;
+  lma_nodes_ge1_ = NULL;
+  homo_idx_buf_ = NULL;
+  spl_table_ = NULL;
+  spl_parser_ = NULL;
+
+  lemma_num_ = 0;
+  lma_nds_used_num_le0_ = 0;
+  lma_nds_used_num_ge1_ = 0;
+  homo_idx_num_eq1_ = 0;
+  homo_idx_num_gt1_ = 0;
+}
+
+size_t DictBuilder::read_raw_dict(const char* fn_raw,
+                                  const char *fn_validhzs,
+                                  size_t max_item) {
+  if (NULL == fn_raw) return 0;
+
+  Utf16Reader utf16_reader;
+  if (!utf16_reader.open(fn_raw, kReadBufLen * 10))
+    return false;
+
+  char16 read_buf[kReadBufLen];
+
+  // Read the number of lemmas in the file
+  size_t lemma_num = 240000;
+
+  // allocate resource required
+  if (!alloc_resource(lemma_num)) {
+    utf16_reader.close();
+  }
+
+  // Read the valid Hanzi list.
+  char16 *valid_hzs = NULL;
+  size_t valid_hzs_num = 0;
+  valid_hzs = read_valid_hanzis(fn_validhzs, &valid_hzs_num);
+
+  // Begin reading the lemma entries
+  for (size_t i = 0; i < max_item; i++) {
+    // read next entry
+    if (!utf16_reader.readline(read_buf, kReadBufLen)) {
+      lemma_num = i;
+      break;
+    }
+
+    size_t token_size;
+    char16 *token;
+    char16 *to_tokenize = read_buf;
+
+    // Get the Hanzi string
+    token = utf16_strtok(to_tokenize, &token_size, &to_tokenize);
+    if (NULL == token) {
+      free_resource();
+      utf16_reader.close();
+      return false;
+    }
+
+    size_t lemma_size = utf16_strlen(token);
+
+    if (lemma_size > kMaxLemmaSize) {
+      i--;
+      continue;
+    }
+
+    if (lemma_size > 4) {
+      i--;
+      continue;
+    }
+
+    // Copy to the lemma entry
+    utf16_strcpy(lemma_arr_[i].hanzi_str, token);
+
+    lemma_arr_[i].hz_str_len = token_size;
+
+    // Get the freq string
+    token = utf16_strtok(to_tokenize, &token_size, &to_tokenize);
+    if (NULL == token) {
+      free_resource();
+      utf16_reader.close();
+      return false;
+    }
+    lemma_arr_[i].freq = utf16_atof(token);
+
+    if (lemma_size > 1 && lemma_arr_[i].freq < 60) {
+      i--;
+      continue;
+    }
+
+    // Get GBK mark, if no valid Hanzi list available, all items which contains
+    // GBK characters will be discarded. Otherwise, all items which contains
+    // characters outside of the valid Hanzi list will be discarded.
+    token = utf16_strtok(to_tokenize, &token_size, &to_tokenize);
+    assert(NULL != token);
+    int gbk_flag = utf16_atoi(token);
+    if (NULL == valid_hzs || 0 == valid_hzs_num) {
+      if (0 != gbk_flag) {
+        i--;
+        continue;
+      }
+    } else {
+      if (!str_in_hanzis_list(valid_hzs, valid_hzs_num,
+          lemma_arr_[i].hanzi_str, lemma_arr_[i].hz_str_len)) {
+        i--;
+        continue;
+      }
+    }
+
+    // Get spelling String
+    bool spelling_not_support = false;
+    for (size_t hz_pos = 0; hz_pos < (size_t)lemma_arr_[i].hz_str_len;
+         hz_pos++) {
+      // Get a Pinyin
+      token = utf16_strtok(to_tokenize, &token_size, &to_tokenize);
+      if (NULL == token) {
+        free_resource();
+        utf16_reader.close();
+        return false;
+      }
+
+      assert(utf16_strlen(token) <= kMaxPinyinSize);
+
+      utf16_strcpy_tochar(lemma_arr_[i].pinyin_str[hz_pos], token);
+
+      format_spelling_str(lemma_arr_[i].pinyin_str[hz_pos]);
+
+      // Put the pinyin to the spelling table
+      if (!spl_table_->put_spelling(lemma_arr_[i].pinyin_str[hz_pos],
+                                    lemma_arr_[i].freq)) {
+        spelling_not_support = true;
+        break;
+      }
+    }
+
+    // The whole line must have been parsed fully, otherwise discard this one.
+    token = utf16_strtok(to_tokenize, &token_size, &to_tokenize);
+    if (spelling_not_support || NULL != token) {
+      i--;
+      continue;
+    }
+    //    printf("i: %d\n", i);
+  }
+
+  delete [] valid_hzs;
+  utf16_reader.close();
+
+  printf("read succesfully, lemma num: %d\n", lemma_num);
+
+  return lemma_num;
+}
+
+bool DictBuilder::build_dict(const char *fn_raw,
+                             const char *fn_validhzs,
+                             DictTrie *dict_trie) {
+  if (NULL == fn_raw || NULL == dict_trie)
+    return false;
+
+  lemma_num_ = read_raw_dict(fn_raw, fn_validhzs, 240000);
+  if (0 == lemma_num_)
+    return false;
+
+  // Arrange the spelling table, and build a spelling tree
+  // The size of an spelling. '\0' is included. If the spelling table is
+  // initialized to calculate the spelling scores, the last char in the
+  // spelling string will be score, and it is also included in spl_item_size.
+  size_t spl_item_size;
+  size_t spl_num;
+  const char* spl_buf;
+  spl_buf = spl_table_->arrange(&spl_item_size, &spl_num);
+  if (NULL == spl_buf) {
+    free_resource();
+    return false;
+  }
+
+  SpellingTrie &spl_trie = SpellingTrie::get_instance();
+
+  if (!spl_trie.construct(spl_buf, spl_item_size, spl_num,
+                          spl_table_->get_score_amplifier(),
+                          spl_table_->get_average_score())) {
+    free_resource();
+    return false;
+  }
+
+  printf("spelling tree construct successfully.\n");
+
+  // Convert the spelling string to idxs
+  for (size_t i = 0; i < lemma_num_; i++) {
+    for (size_t hz_pos = 0; hz_pos < (size_t)lemma_arr_[i].hz_str_len;
+         hz_pos++) {
+      uint16 spl_idxs[2];
+      uint16 spl_start_pos[3];
+      bool is_pre = true;
+      int spl_idx_num =
+        spl_parser_->splstr_to_idxs(lemma_arr_[i].pinyin_str[hz_pos],
+                                    strlen(lemma_arr_[i].pinyin_str[hz_pos]),
+                                    spl_idxs, spl_start_pos, 2, is_pre);
+      assert(1 == spl_idx_num);
+
+      if (spl_trie.is_half_id(spl_idxs[0])) {
+        uint16 num = spl_trie.half_to_full(spl_idxs[0], spl_idxs);
+        assert(0 != num);
+      }
+      lemma_arr_[i].spl_idx_arr[hz_pos] = spl_idxs[0];
+    }
+  }
+
+  // Sort the lemma items according to the hanzi, and give each unique item a
+  // id
+  sort_lemmas_by_hz();
+
+  scis_num_ = build_scis();
+
+  // Construct the dict list
+  dict_trie->dict_list_ = new DictList();
+  bool dl_success = dict_trie->dict_list_->init_list(scis_, scis_num_,
+                                                     lemma_arr_, lemma_num_);
+  assert(dl_success);
+
+  // Construct the NGram information
+  NGram& ngram = NGram::get_instance();
+  ngram.build_unigram(lemma_arr_, lemma_num_,
+                      lemma_arr_[lemma_num_ - 1].idx_by_hz + 1);
+
+  // sort the lemma items according to the spelling idx string
+  myqsort(lemma_arr_, lemma_num_, sizeof(LemmaEntry), compare_py);
+
+  get_top_lemmas();
+
+  // printf("Now begin construct tree\n");
+#ifdef ___DO_STATISTICS___
+  stat_init();
+#endif
+
+  lma_nds_used_num_le0_ = 1;  // The root node
+  bool dt_success = construct_subset(static_cast<void*>(lma_nodes_le0_),
+                                     lemma_arr_, 0, lemma_num_, 0);
+  if (!dt_success) {
+    free_resource();
+    return false;
+  }
+
+#ifdef ___DO_STATISTICS___
+  stat_print();
+#endif
+
+  // Move the node data and homo data to the DictTrie
+  dict_trie->root_ = new LmaNodeLE0[lma_nds_used_num_le0_];
+  dict_trie->nodes_ge1_ = new LmaNodeGE1[lma_nds_used_num_ge1_];
+  size_t lma_idx_num = homo_idx_num_eq1_ + homo_idx_num_gt1_ + top_lmas_num_;
+  dict_trie->lma_idx_buf_ = new unsigned char[lma_idx_num * kLemmaIdSize];
+  assert(NULL != dict_trie->root_);
+  assert(NULL != dict_trie->lma_idx_buf_);
+  dict_trie->lma_node_num_le0_ = lma_nds_used_num_le0_;
+  dict_trie->lma_node_num_ge1_ = lma_nds_used_num_ge1_;
+  dict_trie->lma_idx_buf_len_ = lma_idx_num * kLemmaIdSize;
+  dict_trie->top_lmas_num_ = top_lmas_num_;
+
+  memcpy(dict_trie->root_, lma_nodes_le0_,
+         sizeof(LmaNodeLE0) * lma_nds_used_num_le0_);
+  memcpy(dict_trie->nodes_ge1_, lma_nodes_ge1_,
+         sizeof(LmaNodeGE1) * lma_nds_used_num_ge1_);
+
+  for (size_t pos = 0; pos < homo_idx_num_eq1_ + homo_idx_num_gt1_; pos++) {
+    id_to_charbuf(dict_trie->lma_idx_buf_ + pos * kLemmaIdSize,
+                  homo_idx_buf_[pos]);
+  }
+
+  for (size_t pos = homo_idx_num_eq1_ + homo_idx_num_gt1_;
+       pos < lma_idx_num; pos++) {
+    LemmaIdType idx =
+        top_lmas_[pos - homo_idx_num_eq1_ - homo_idx_num_gt1_].idx_by_hz;
+    id_to_charbuf(dict_trie->lma_idx_buf_ + pos * kLemmaIdSize, idx);
+  }
+
+  if (kPrintDebug0) {
+    printf("homo_idx_num_eq1_: %d\n", homo_idx_num_eq1_);
+    printf("homo_idx_num_gt1_: %d\n", homo_idx_num_gt1_);
+    printf("top_lmas_num_: %d\n", top_lmas_num_);
+  }
+
+  free_resource();
+
+  if (kPrintDebug0) {
+    printf("Building dict succeds\n");
+  }
+  return dt_success;
+}
+
+void DictBuilder::id_to_charbuf(unsigned char *buf, LemmaIdType id) {
+  if (NULL == buf) return;
+  for (size_t pos = 0; pos < kLemmaIdSize; pos++) {
+    (buf)[pos] = (unsigned char)(id >> (pos * 8));
+  }
+}
+
+void DictBuilder::set_son_offset(LmaNodeGE1 *node, size_t offset) {
+  node->son_1st_off_l = static_cast<uint16>(offset);
+  node->son_1st_off_h = static_cast<unsigned char>(offset >> 16);
+}
+
+void DictBuilder:: set_homo_id_buf_offset(LmaNodeGE1 *node, size_t offset) {
+  node->homo_idx_buf_off_l = static_cast<uint16>(offset);
+  node->homo_idx_buf_off_h = static_cast<unsigned char>(offset >> 16);
+
+}
+
+// All spelling strings will be converted to upper case, except that
+// spellings started with "ZH"/"CH"/"SH" will be converted to
+// "Zh"/"Ch"/"Sh"
+void DictBuilder::format_spelling_str(char *spl_str) {
+  if (NULL == spl_str)
+    return;
+
+  uint16 pos = 0;
+  while ('\0' != spl_str[pos]) {
+    if (spl_str[pos] >= 'a' && spl_str[pos] <= 'z')
+      spl_str[pos] = spl_str[pos] - 'a' + 'A';
+
+    if (1 == pos && 'H' == spl_str[pos]) {
+      if ('C' == spl_str[0] || 'S' == spl_str[0] || 'Z' == spl_str[0]) {
+        spl_str[pos] = 'h';
+      }
+    }
+    pos++;
+  }
+}
+
+LemmaIdType DictBuilder::sort_lemmas_by_hz() {
+  if (NULL == lemma_arr_ || 0 == lemma_num_)
+    return 0;
+
+  myqsort(lemma_arr_, lemma_num_, sizeof(LemmaEntry), cmp_lemma_entry_hzs);
+
+  lemma_arr_[0].idx_by_hz = 1;
+  LemmaIdType idx_max = 1;
+  for (size_t i = 1; i < lemma_num_; i++) {
+    if (utf16_strcmp(lemma_arr_[i].hanzi_str, lemma_arr_[i-1].hanzi_str)) {
+      idx_max++;
+      lemma_arr_[i].idx_by_hz = idx_max;
+    } else {
+      idx_max++;
+      lemma_arr_[i].idx_by_hz = idx_max;
+    }
+  }
+  return idx_max + 1;
+}
+
+size_t DictBuilder::build_scis() {
+  if (NULL == scis_ || lemma_num_ * kMaxLemmaSize > scis_num_)
+    return 0;
+
+  SpellingTrie &spl_trie = SpellingTrie::get_instance();
+
+  // This first one is blank, because id 0 is invalid.
+  scis_[0].freq = 0;
+  scis_[0].hz = 0;
+  scis_[0].splid.full_splid = 0;
+  scis_[0].splid.half_splid = 0;
+  scis_num_ = 1;
+
+  // Copy the hanzis to the buffer
+  for (size_t pos = 0; pos < lemma_num_; pos++) {
+    size_t hz_num = lemma_arr_[pos].hz_str_len;
+    for (size_t hzpos = 0; hzpos < hz_num; hzpos++) {
+      scis_[scis_num_].hz = lemma_arr_[pos].hanzi_str[hzpos];
+      scis_[scis_num_].splid.full_splid = lemma_arr_[pos].spl_idx_arr[hzpos];
+      scis_[scis_num_].splid.half_splid =
+          spl_trie.full_to_half(scis_[scis_num_].splid.full_splid);
+      if (1 == hz_num)
+        scis_[scis_num_].freq = lemma_arr_[pos].freq;
+      else
+        scis_[scis_num_].freq = 0.000001;
+      scis_num_++;
+    }
+  }
+
+  myqsort(scis_, scis_num_, sizeof(SingleCharItem), cmp_scis_hz_splid_freq);
+
+  // Remove repeated items
+  size_t unique_scis_num = 1;
+  for (size_t pos = 1; pos < scis_num_; pos++) {
+    if (scis_[pos].hz == scis_[pos - 1].hz &&
+        scis_[pos].splid.full_splid == scis_[pos - 1].splid.full_splid)
+      continue;
+    scis_[unique_scis_num] = scis_[pos];
+    scis_[unique_scis_num].splid.half_splid =
+        spl_trie.full_to_half(scis_[pos].splid.full_splid);
+    unique_scis_num++;
+  }
+
+  scis_num_ = unique_scis_num;
+
+  // Update the lemma list.
+  for (size_t pos = 0; pos < lemma_num_; pos++) {
+    size_t hz_num = lemma_arr_[pos].hz_str_len;
+    for (size_t hzpos = 0; hzpos < hz_num; hzpos++) {
+      SingleCharItem key;
+      key.hz = lemma_arr_[pos].hanzi_str[hzpos];
+      key.splid.full_splid = lemma_arr_[pos].spl_idx_arr[hzpos];
+      key.splid.half_splid = spl_trie.full_to_half(key.splid.full_splid);
+
+      SingleCharItem *found;
+      found = static_cast<SingleCharItem*>(mybsearch(&key, scis_,
+                                                     unique_scis_num,
+                                                     sizeof(SingleCharItem),
+                                                     cmp_scis_hz_splid));
+
+      assert(found);
+
+      lemma_arr_[pos].hanzi_scis_ids[hzpos] =
+          static_cast<uint16>(found - scis_);
+      lemma_arr_[pos].spl_idx_arr[hzpos] = found->splid.full_splid;
+    }
+  }
+
+  return scis_num_;
+}
+
+bool DictBuilder::construct_subset(void* parent, LemmaEntry* lemma_arr,
+                                   size_t item_start, size_t item_end,
+                                   size_t level) {
+  if (level >= kMaxLemmaSize || item_end <= item_start)
+    return false;
+
+  // printf("++ enter recursive\n");
+
+  // 1. Scan for how many sons
+  size_t parent_son_num = 0;
+  // LemmaNode *son_1st = NULL;
+  // parent.num_of_son = 0;
+
+  LemmaEntry *lma_last_start = lemma_arr_ + item_start;
+  uint16 spl_idx_node = lma_last_start->spl_idx_arr[level];
+
+  // Scan for how many sons to be allocaed
+  for (size_t i = item_start + 1; i< item_end; i++) {
+    LemmaEntry *lma_current = lemma_arr + i;
+    uint16 spl_idx_current = lma_current->spl_idx_arr[level];
+    if (spl_idx_current != spl_idx_node) {
+      parent_son_num++;
+      spl_idx_node = spl_idx_current;
+    }
+  }
+  parent_son_num++;
+
+#ifdef ___DO_STATISTICS___
+  // Use to indicate whether all nodes of this layer have no son.
+  bool allson_noson = true;
+
+  assert(level < kMaxLemmaSize);
+  if (parent_son_num > max_sonbuf_len_[level])
+    max_sonbuf_len_[level] = parent_son_num;
+
+  total_son_num_[level] += parent_son_num;
+  total_sonbuf_num_[level] += 1;
+
+  if (parent_son_num == 1)
+    sonbufs_num1_++;
+  else
+    sonbufs_numgt1_++;
+  total_lma_node_num_ += parent_son_num;
+#endif
+
+  // 2. Update the parent's information
+  //    Update the parent's son list;
+  LmaNodeLE0 *son_1st_le0 = NULL;  // only one of le0 or ge1 is used
+  LmaNodeGE1 *son_1st_ge1 = NULL;  // only one of le0 or ge1 is used.
+  if (0 == level) {                 // the parent is root
+    (static_cast<LmaNodeLE0*>(parent))->son_1st_off =
+      lma_nds_used_num_le0_;
+    son_1st_le0 = lma_nodes_le0_ + lma_nds_used_num_le0_;
+    lma_nds_used_num_le0_ += parent_son_num;
+
+    assert(parent_son_num <= 65535);
+    (static_cast<LmaNodeLE0*>(parent))->num_of_son =
+      static_cast<uint16>(parent_son_num);
+  } else if (1 == level) {  // the parent is a son of root
+    (static_cast<LmaNodeLE0*>(parent))->son_1st_off =
+      lma_nds_used_num_ge1_;
+    son_1st_ge1 = lma_nodes_ge1_ + lma_nds_used_num_ge1_;
+    lma_nds_used_num_ge1_ += parent_son_num;
+
+    assert(parent_son_num <= 65535);
+    (static_cast<LmaNodeLE0*>(parent))->num_of_son =
+      static_cast<uint16>(parent_son_num);
+  } else {
+    set_son_offset((static_cast<LmaNodeGE1*>(parent)),
+                   lma_nds_used_num_ge1_);
+    son_1st_ge1 = lma_nodes_ge1_ + lma_nds_used_num_ge1_;
+    lma_nds_used_num_ge1_ += parent_son_num;
+
+    assert(parent_son_num <= 255);
+    (static_cast<LmaNodeGE1*>(parent))->num_of_son =
+      (unsigned char)parent_son_num;
+  }
+
+  // 3. Now begin to construct the son one by one
+  size_t son_pos = 0;
+
+  lma_last_start = lemma_arr_ + item_start;
+  spl_idx_node = lma_last_start->spl_idx_arr[level];
+
+  //  printf("++ spl_idx_node: %d\n", spl_idx_node);
+
+  size_t homo_num = 0;
+  if (lma_last_start->spl_idx_arr[level + 1] == 0)
+    homo_num = 1;
+
+  size_t item_start_next = item_start;
+
+  for (size_t i = item_start + 1; i < item_end; i++) {
+    LemmaEntry* lma_current = lemma_arr_ + i;
+    uint16 spl_idx_current = lma_current->spl_idx_arr[level];
+
+    if (spl_idx_current == spl_idx_node) {
+      if (lma_current->spl_idx_arr[level + 1] == 0)
+        homo_num++;
+    } else {
+      // Construct a node
+      LmaNodeLE0 *node_cur_le0 = NULL;  // only one of them is valid
+      LmaNodeGE1 *node_cur_ge1 = NULL;
+      if (0 == level) {
+        node_cur_le0 = son_1st_le0 + son_pos;
+        node_cur_le0->spl_idx = spl_idx_node;
+        node_cur_le0->homo_idx_buf_off = homo_idx_num_eq1_ + homo_idx_num_gt1_;
+        node_cur_le0->son_1st_off = 0;
+        homo_idx_num_eq1_ += homo_num;
+      } else {
+        node_cur_ge1 = son_1st_ge1 + son_pos;
+        node_cur_ge1->spl_idx = spl_idx_node;
+
+        set_homo_id_buf_offset(node_cur_ge1,
+                               (homo_idx_num_eq1_ + homo_idx_num_gt1_));
+        set_son_offset(node_cur_ge1, 0);
+        homo_idx_num_gt1_ += homo_num;
+      }
+
+      // printf("++ homo_num: %d\n++ lmaids: ", homo_num);
+
+      if (homo_num > 0) {
+        LemmaIdType* idx_buf = homo_idx_buf_ + homo_idx_num_eq1_ +
+              homo_idx_num_gt1_ - homo_num;
+        if (0 == level) {
+          assert(homo_num <= 65535);
+          node_cur_le0->num_of_homo = static_cast<uint16>(homo_num);
+        } else {
+          assert(homo_num <= 255);
+          node_cur_ge1->num_of_homo = (unsigned char)homo_num;
+        }
+
+        for (size_t homo_pos = 0; homo_pos < homo_num; homo_pos++) {
+          idx_buf[homo_pos] = lemma_arr_[item_start_next + homo_pos].idx_by_hz;
+          // printf("[%d] %d ", item_start_next + homo_pos,  idx_buf[homo_pos]);
+        }
+
+        // printf("\n");
+#ifdef ___DO_STATISTICS___
+        if (homo_num > max_homobuf_len_[level])
+          max_homobuf_len_[level] = homo_num;
+
+        total_homo_num_[level] += homo_num;
+#endif
+      }
+
+      if (i - item_start_next > homo_num) {
+        void *next_parent;
+        if (0 == level)
+          next_parent = static_cast<void*>(node_cur_le0);
+        else
+          next_parent = static_cast<void*>(node_cur_ge1);
+        construct_subset(next_parent, lemma_arr,
+                         item_start_next + homo_num, i, level + 1);
+#ifdef ___DO_STATISTICS___
+
+        total_node_hasson_[level] += 1;
+        allson_noson = false;
+#endif
+      }
+
+      // for the next son
+      lma_last_start = lma_current;
+      spl_idx_node = spl_idx_current;
+      item_start_next = i;
+      homo_num = 0;
+      if (lma_current->spl_idx_arr[level + 1] == 0)
+        homo_num = 1;
+
+      son_pos++;
+    }
+  }
+
+  // 4. The last one to construct
+  LmaNodeLE0 *node_cur_le0 = NULL;  // only one of them is valid
+  LmaNodeGE1 *node_cur_ge1 = NULL;
+  if (0 == level) {
+    node_cur_le0 = son_1st_le0 + son_pos;
+    node_cur_le0->spl_idx = spl_idx_node;
+    node_cur_le0->homo_idx_buf_off = homo_idx_num_eq1_ + homo_idx_num_gt1_;
+    node_cur_le0->son_1st_off = 0;
+    homo_idx_num_eq1_ += homo_num;
+  } else {
+    node_cur_ge1 = son_1st_ge1 + son_pos;
+    node_cur_ge1->spl_idx = spl_idx_node;
+
+    set_homo_id_buf_offset(node_cur_ge1,
+                           (homo_idx_num_eq1_ + homo_idx_num_gt1_));
+    set_son_offset(node_cur_ge1, 0);
+    homo_idx_num_gt1_ += homo_num;
+  }
+
+  if (homo_num > 0) {
+    LemmaIdType* idx_buf = homo_idx_buf_ + homo_idx_num_eq1_ +
+          homo_idx_num_gt1_ - homo_num;
+    if (0 == level) {
+      assert(homo_num <= 65535);
+      node_cur_le0->num_of_homo = static_cast<uint16>(homo_num);
+    } else {
+      assert(homo_num <= 255);
+      node_cur_ge1->num_of_homo = (unsigned char)homo_num;
+    }
+
+    for (size_t homo_pos = 0; homo_pos < homo_num; homo_pos++) {
+      idx_buf[homo_pos] = lemma_arr[item_start_next + homo_pos].idx_by_hz;
+    }
+
+#ifdef ___DO_STATISTICS___
+    if (homo_num > max_homobuf_len_[level])
+      max_homobuf_len_[level] = homo_num;
+
+    total_homo_num_[level] += homo_num;
+#endif
+  }
+
+  if (item_end - item_start_next > homo_num) {
+    void *next_parent;
+    if (0 == level)
+      next_parent = static_cast<void*>(node_cur_le0);
+    else
+      next_parent = static_cast<void*>(node_cur_ge1);
+    construct_subset(next_parent, lemma_arr,
+                     item_start_next + homo_num, item_end, level + 1);
+#ifdef ___DO_STATISTICS___
+
+    total_node_hasson_[level] += 1;
+    allson_noson = false;
+#endif
+  }
+
+#ifdef ___DO_STATISTICS___
+  if (allson_noson) {
+    total_sonbuf_allnoson_[level] += 1;
+    total_node_in_sonbuf_allnoson_[level] += parent_son_num;
+  }
+#endif
+
+  assert(son_pos + 1 == parent_son_num);
+  return true;
+}
+
+#ifdef ___DO_STATISTICS___
+void DictBuilder::stat_init() {
+  memset(max_sonbuf_len_, 0, sizeof(size_t) * kMaxLemmaSize);
+  memset(max_homobuf_len_, 0, sizeof(size_t) * kMaxLemmaSize);
+  memset(total_son_num_, 0, sizeof(size_t) * kMaxLemmaSize);
+  memset(total_node_hasson_, 0, sizeof(size_t) * kMaxLemmaSize);
+  memset(total_sonbuf_num_, 0, sizeof(size_t) * kMaxLemmaSize);
+  memset(total_sonbuf_allnoson_, 0, sizeof(size_t) * kMaxLemmaSize);
+  memset(total_node_in_sonbuf_allnoson_, 0, sizeof(size_t) * kMaxLemmaSize);
+  memset(total_homo_num_, 0, sizeof(size_t) * kMaxLemmaSize);
+
+  sonbufs_num1_ = 0;
+  sonbufs_numgt1_ = 0;
+  total_lma_node_num_ = 0;
+}
+
+void DictBuilder::stat_print() {
+  printf("\n------------STAT INFO-------------\n");
+  printf("[root is layer -1]\n");
+  printf(".. max_sonbuf_len per layer(from layer 0):\n   ");
+  for (size_t i = 0; i < kMaxLemmaSize; i++)
+    printf("%d, ", max_sonbuf_len_[i]);
+  printf("-, \n");
+
+  printf(".. max_homobuf_len per layer:\n   -, ");
+  for (size_t i = 0; i < kMaxLemmaSize; i++)
+    printf("%d, ", max_homobuf_len_[i]);
+  printf("\n");
+
+  printf(".. total_son_num per layer:\n   ");
+  for (size_t i = 0; i < kMaxLemmaSize; i++)
+    printf("%d, ", total_son_num_[i]);
+  printf("-, \n");
+
+  printf(".. total_node_hasson per layer:\n   1, ");
+  for (size_t i = 0; i < kMaxLemmaSize; i++)
+    printf("%d, ", total_node_hasson_[i]);
+  printf("\n");
+
+  printf(".. total_sonbuf_num per layer:\n   ");
+  for (size_t i = 0; i < kMaxLemmaSize; i++)
+    printf("%d, ", total_sonbuf_num_[i]);
+  printf("-, \n");
+
+  printf(".. total_sonbuf_allnoson per layer:\n   ");
+  for (size_t i = 0; i < kMaxLemmaSize; i++)
+    printf("%d, ", total_sonbuf_allnoson_[i]);
+  printf("-, \n");
+
+  printf(".. total_node_in_sonbuf_allnoson per layer:\n   ");
+  for (size_t i = 0; i < kMaxLemmaSize; i++)
+    printf("%d, ", total_node_in_sonbuf_allnoson_[i]);
+  printf("-, \n");
+
+  printf(".. total_homo_num per layer:\n   0, ");
+  for (size_t i = 0; i < kMaxLemmaSize; i++)
+    printf("%d, ", total_homo_num_[i]);
+  printf("\n");
+
+  printf(".. son buf allocation number with only 1 son: %d\n", sonbufs_num1_);
+  printf(".. son buf allocation number with more than 1 son: %d\n",
+         sonbufs_numgt1_);
+  printf(".. total lemma node number: %d\n", total_lma_node_num_ + 1);
+}
+#endif  // ___DO_STATISTICS___
+
+#endif  // ___BUILD_MODEL___
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/dictlist.cpp b/PinyinIME/jni/share/dictlist.cpp
new file mode 100644
index 0000000..aa7905c
--- /dev/null
+++ b/PinyinIME/jni/share/dictlist.cpp
@@ -0,0 +1,446 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "../include/dictlist.h"
+#include "../include/mystdlib.h"
+#include "../include/ngram.h"
+#include "../include/searchutility.h"
+
+namespace ime_pinyin {
+
+DictList::DictList() {
+  initialized_ = false;
+  scis_num_ = 0;
+  scis_hz_ = NULL;
+  scis_splid_ = NULL;
+  buf_ = NULL;
+  spl_trie_ = SpellingTrie::get_cpinstance();
+
+  assert(kMaxLemmaSize == 8);
+  cmp_func_[0] = cmp_hanzis_1;
+  cmp_func_[1] = cmp_hanzis_2;
+  cmp_func_[2] = cmp_hanzis_3;
+  cmp_func_[3] = cmp_hanzis_4;
+  cmp_func_[4] = cmp_hanzis_5;
+  cmp_func_[5] = cmp_hanzis_6;
+  cmp_func_[6] = cmp_hanzis_7;
+  cmp_func_[7] = cmp_hanzis_8;
+}
+
+DictList::~DictList() {
+  free_resource();
+}
+
+bool DictList::alloc_resource(size_t buf_size, size_t scis_num) {
+  // Allocate memory
+  buf_ = static_cast<char16*>(malloc(buf_size * sizeof(char16)));
+  if (NULL == buf_)
+    return false;
+
+  scis_num_ = scis_num;
+
+  scis_hz_ = static_cast<char16*>(malloc(scis_num_ * sizeof(char16)));
+  if (NULL == scis_hz_)
+    return false;
+
+  scis_splid_ = static_cast<SpellingId*>
+      (malloc(scis_num_ * sizeof(SpellingId)));
+
+  if (NULL == scis_splid_)
+    return false;
+
+  return true;
+}
+
+void DictList::free_resource() {
+  if (NULL != buf_)
+    free(buf_);
+  buf_ = NULL;
+
+  if (NULL != scis_hz_)
+    free(scis_hz_);
+  scis_hz_ = NULL;
+
+  if (NULL != scis_splid_)
+    free(scis_splid_);
+  scis_splid_ = NULL;
+}
+
+#ifdef ___BUILD_MODEL___
+bool DictList::init_list(const SingleCharItem *scis, size_t scis_num,
+                         const LemmaEntry *lemma_arr, size_t lemma_num) {
+  if (NULL == scis || 0 == scis_num || NULL == lemma_arr || 0 == lemma_num)
+    return false;
+
+  initialized_ = false;
+
+  if (NULL != buf_)
+    free(buf_);
+
+  // calculate the size
+  size_t buf_size = calculate_size(lemma_arr, lemma_num);
+  if (0 == buf_size)
+    return false;
+
+  if (!alloc_resource(buf_size, scis_num))
+    return false;
+
+  fill_scis(scis, scis_num);
+
+  // Copy the related content from the array to inner buffer
+  fill_list(lemma_arr, lemma_num);
+
+  initialized_ = true;
+  return true;
+}
+
+size_t DictList::calculate_size(const LemmaEntry* lemma_arr, size_t lemma_num) {
+  size_t last_hz_len = 0;
+  size_t list_size = 0;
+  size_t id_num = 0;
+
+  for (size_t i = 0; i < lemma_num; i++) {
+    if (0 == i) {
+      last_hz_len = lemma_arr[i].hz_str_len;
+
+      assert(last_hz_len > 0);
+      assert(lemma_arr[0].idx_by_hz == 1);
+
+      id_num++;
+      start_pos_[0] = 0;
+      start_id_[0] = id_num;
+
+      last_hz_len = 1;
+      list_size += last_hz_len;
+    } else {
+      size_t current_hz_len = lemma_arr[i].hz_str_len;
+
+      assert(current_hz_len >= last_hz_len);
+
+      if (current_hz_len == last_hz_len) {
+          list_size += current_hz_len;
+          id_num++;
+      } else {
+        for (size_t len = last_hz_len; len < current_hz_len - 1; len++) {
+          start_pos_[len] = start_pos_[len - 1];
+          start_id_[len] = start_id_[len - 1];
+        }
+
+        start_pos_[current_hz_len - 1] = list_size;
+
+        id_num++;
+        start_id_[current_hz_len - 1] = id_num;
+
+        last_hz_len = current_hz_len;
+        list_size += current_hz_len;
+      }
+    }
+  }
+
+  for (size_t i = last_hz_len; i <= kMaxLemmaSize; i++) {
+    if (0 == i) {
+      start_pos_[0] = 0;
+      start_id_[0] = 1;
+    } else {
+      start_pos_[i] = list_size;
+      start_id_[i] = id_num;
+    }
+  }
+
+  return start_pos_[kMaxLemmaSize];
+}
+
+void DictList::fill_scis(const SingleCharItem *scis, size_t scis_num) {
+  assert(scis_num_ == scis_num);
+
+  for (size_t pos = 0; pos < scis_num_; pos++) {
+    scis_hz_[pos] = scis[pos].hz;
+    scis_splid_[pos] = scis[pos].splid;
+  }
+}
+
+void DictList::fill_list(const LemmaEntry* lemma_arr, size_t lemma_num) {
+  size_t current_pos = 0;
+
+  utf16_strncpy(buf_, lemma_arr[0].hanzi_str,
+                lemma_arr[0].hz_str_len);
+
+  current_pos = lemma_arr[0].hz_str_len;
+
+  size_t id_num = 1;
+
+  for (size_t i = 1; i < lemma_num; i++) {
+    utf16_strncpy(buf_ + current_pos, lemma_arr[i].hanzi_str,
+                  lemma_arr[i].hz_str_len);
+
+    id_num++;
+    current_pos += lemma_arr[i].hz_str_len;
+  }
+
+  assert(current_pos == start_pos_[kMaxLemmaSize]);
+  assert(id_num == start_id_[kMaxLemmaSize]);
+}
+
+char16* DictList::find_pos2_startedbyhz(char16 hz_char) {
+  char16 *found_2w = static_cast<char16*>
+                     (mybsearch(&hz_char, buf_ + start_pos_[1],
+                                (start_pos_[2] - start_pos_[1]) / 2,
+                                sizeof(char16) * 2, cmp_hanzis_1));
+  if (NULL == found_2w)
+    return NULL;
+
+  while (found_2w > buf_ + start_pos_[1] && *found_2w == *(found_2w - 1))
+    found_2w -= 2;
+
+  return found_2w;
+}
+#endif  // ___BUILD_MODEL___
+
+char16* DictList::find_pos_startedbyhzs(const char16 last_hzs[],
+    size_t word_len, int (*cmp_func)(const void *, const void *)) {
+  char16 *found_w = static_cast<char16*>
+                    (mybsearch(last_hzs, buf_ + start_pos_[word_len - 1],
+                               (start_pos_[word_len] - start_pos_[word_len - 1])
+                               / word_len,
+                               sizeof(char16) * word_len, cmp_func));
+
+  if (NULL == found_w)
+    return NULL;
+
+  while (found_w > buf_ + start_pos_[word_len -1] &&
+         cmp_func(found_w, found_w - word_len) == 0)
+    found_w -= word_len;
+
+  return found_w;
+}
+
+size_t DictList::predict(const char16 last_hzs[], uint16 hzs_len,
+                         NPredictItem *npre_items, size_t npre_max,
+                         size_t b4_used) {
+  assert(hzs_len <= kMaxPredictSize && hzs_len > 0);
+
+  // 1. Prepare work
+  int (*cmp_func)(const void *, const void *) = cmp_func_[hzs_len - 1];
+
+  NGram& ngram = NGram::get_instance();
+
+  size_t item_num = 0;
+
+  // 2. Do prediction
+  for (uint16 pre_len = 1; pre_len <= kMaxPredictSize + 1 - hzs_len;
+       pre_len++) {
+    uint16 word_len = hzs_len + pre_len;
+    char16 *w_buf = find_pos_startedbyhzs(last_hzs, word_len, cmp_func);
+    if (NULL == w_buf)
+      continue;
+    while (w_buf < buf_ + start_pos_[word_len] &&
+           cmp_func(w_buf, last_hzs) == 0 &&
+           item_num < npre_max) {
+      memset(npre_items + item_num, 0, sizeof(NPredictItem));
+      utf16_strncpy(npre_items[item_num].pre_hzs, w_buf + hzs_len, pre_len);
+      npre_items[item_num].psb =
+        ngram.get_uni_psb((size_t)(w_buf - buf_ - start_pos_[word_len - 1])
+                          / word_len + start_id_[word_len - 1]);
+      npre_items[item_num].his_len = hzs_len;
+      item_num++;
+      w_buf += word_len;
+    }
+  }
+
+  size_t new_num = 0;
+  for (size_t i = 0; i < item_num; i++) {
+    // Try to find it in the existing items
+    size_t e_pos;
+    for (e_pos = 1; e_pos <= b4_used; e_pos++) {
+      if (utf16_strncmp((*(npre_items - e_pos)).pre_hzs, npre_items[i].pre_hzs,
+                        kMaxPredictSize) == 0)
+        break;
+    }
+    if (e_pos <= b4_used)
+      continue;
+
+    // If not found, append it to the buffer
+    npre_items[new_num] = npre_items[i];
+    new_num++;
+  }
+
+  return new_num;
+}
+
+uint16 DictList::get_lemma_str(LemmaIdType id_lemma, char16 *str_buf,
+                               uint16 str_max) {
+  if (!initialized_ || id_lemma >= start_id_[kMaxLemmaSize] || NULL == str_buf
+      || str_max <= 1)
+    return 0;
+
+  // Find the range
+  for (uint16 i = 0; i < kMaxLemmaSize; i++) {
+    if (i + 1 > str_max - 1)
+      return 0;
+    if (start_id_[i] <= id_lemma && start_id_[i + 1] > id_lemma) {
+      size_t id_span = id_lemma - start_id_[i];
+
+      uint16 *buf = buf_ + start_pos_[i] + id_span * (i + 1);
+      for (uint16 len = 0; len <= i; len++) {
+        str_buf[len] = buf[len];
+      }
+      str_buf[i+1] = (char16)'\0';
+      return i + 1;
+    }
+  }
+  return 0;
+}
+
+uint16 DictList::get_splids_for_hanzi(char16 hanzi, uint16 half_splid,
+                                      uint16 *splids, uint16 max_splids) {
+  char16 *hz_found = static_cast<char16*>
+      (mybsearch(&hanzi, scis_hz_, scis_num_, sizeof(char16), cmp_hanzis_1));
+  assert(NULL != hz_found && hanzi == *hz_found);
+
+  // Move to the first one.
+  while (hz_found > scis_hz_ && hanzi == *(hz_found - 1))
+    hz_found--;
+
+  // First try to found if strict comparison result is not zero.
+  char16 *hz_f = hz_found;
+  bool strict = false;
+  while (hz_f < scis_hz_ + scis_num_ && hanzi == *hz_f) {
+    uint16 pos = hz_f - scis_hz_;
+    if (0 == half_splid || scis_splid_[pos].half_splid == half_splid) {
+      strict = true;
+    }
+    hz_f++;
+  }
+
+  uint16 found_num = 0;
+  while (hz_found < scis_hz_ + scis_num_ && hanzi == *hz_found) {
+    uint16 pos = hz_found - scis_hz_;
+    if (0 == half_splid ||
+        (strict && scis_splid_[pos].half_splid == half_splid) ||
+        (!strict && spl_trie_->half_full_compatible(half_splid,
+        scis_splid_[pos].full_splid))) {
+      assert(found_num + 1 < max_splids);
+      splids[found_num] = scis_splid_[pos].full_splid;
+      found_num++;
+    }
+    hz_found++;
+  }
+
+  return found_num;
+}
+
+LemmaIdType DictList::get_lemma_id(const char16 *str, uint16 str_len) {
+  if (NULL == str || str_len > kMaxLemmaSize)
+    return 0;
+
+  char16 *found = find_pos_startedbyhzs(str, str_len, cmp_func_[str_len - 1]);
+  if (NULL == found)
+    return 0;
+
+  assert(found > buf_);
+  assert(static_cast<size_t>(found - buf_) >= start_pos_[str_len - 1]);
+  return static_cast<LemmaIdType>
+      (start_id_[str_len - 1] +
+       (found - buf_ - start_pos_[str_len - 1]) / str_len);
+}
+
+void DictList::convert_to_hanzis(char16 *str, uint16 str_len) {
+  assert(NULL != str);
+
+  for (uint16 str_pos = 0; str_pos < str_len; str_pos++) {
+    str[str_pos] = scis_hz_[str[str_pos]];
+  }
+}
+
+void DictList::convert_to_scis_ids(char16 *str, uint16 str_len) {
+  assert(NULL != str);
+
+  for (uint16 str_pos = 0; str_pos < str_len; str_pos++) {
+    str[str_pos] = 0x100;
+  }
+}
+
+bool DictList::save_list(FILE *fp) {
+  if (!initialized_ || NULL == fp)
+    return false;
+
+  if (NULL == buf_ || 0 == start_pos_[kMaxLemmaSize] ||
+      NULL == scis_hz_ || NULL == scis_splid_ || 0 == scis_num_)
+    return false;
+
+  if (fwrite(&scis_num_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fwrite(start_pos_, sizeof(size_t), kMaxLemmaSize + 1, fp) !=
+      kMaxLemmaSize + 1)
+    return false;
+
+  if (fwrite(start_id_, sizeof(size_t), kMaxLemmaSize + 1, fp) !=
+      kMaxLemmaSize + 1)
+    return false;
+
+  if (fwrite(scis_hz_, sizeof(char16), scis_num_, fp) != scis_num_)
+    return false;
+
+  if (fwrite(scis_splid_, sizeof(SpellingId), scis_num_, fp) != scis_num_)
+    return false;
+
+  if (fwrite(buf_, sizeof(char16), start_pos_[kMaxLemmaSize], fp) !=
+      start_pos_[kMaxLemmaSize])
+    return false;
+
+  return true;
+}
+
+bool DictList::load_list(FILE *fp) {
+  if (NULL == fp)
+    return false;
+
+  initialized_ = false;
+
+  if (fread(&scis_num_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fread(start_pos_, sizeof(size_t), kMaxLemmaSize + 1, fp) !=
+      kMaxLemmaSize + 1)
+    return false;
+
+  if (fread(start_id_, sizeof(size_t), kMaxLemmaSize + 1, fp) !=
+      kMaxLemmaSize + 1)
+    return false;
+
+  free_resource();
+
+  if (!alloc_resource(start_pos_[kMaxLemmaSize], scis_num_))
+    return false;
+
+  if (fread(scis_hz_, sizeof(char16), scis_num_, fp) != scis_num_)
+    return false;
+
+  if (fread(scis_splid_, sizeof(SpellingId), scis_num_, fp) != scis_num_)
+    return false;
+
+  if (fread(buf_, sizeof(char16), start_pos_[kMaxLemmaSize], fp) !=
+      start_pos_[kMaxLemmaSize])
+    return false;
+
+  initialized_ = true;
+  return true;
+}
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/dicttrie.cpp b/PinyinIME/jni/share/dicttrie.cpp
new file mode 100644
index 0000000..88b819d
--- /dev/null
+++ b/PinyinIME/jni/share/dicttrie.cpp
@@ -0,0 +1,942 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include "../include/dicttrie.h"
+#include "../include/dictbuilder.h"
+#include "../include/lpicache.h"
+#include "../include/mystdlib.h"
+#include "../include/ngram.h"
+
+namespace ime_pinyin {
+
+DictTrie::DictTrie() {
+  spl_trie_ = SpellingTrie::get_cpinstance();
+
+  root_ = NULL;
+  splid_le0_index_ = NULL;
+  lma_node_num_le0_ = 0;
+  nodes_ge1_ = NULL;
+  lma_node_num_ge1_ = 0;
+  lma_idx_buf_ = NULL;
+  lma_idx_buf_len_ = 0;
+  total_lma_num_ = 0;
+  top_lmas_num_ = 0;
+  dict_list_ = NULL;
+
+  parsing_marks_ = NULL;
+  mile_stones_ = NULL;
+  reset_milestones(0, kFirstValidMileStoneHandle);
+}
+
+DictTrie::~DictTrie() {
+  free_resource(true);
+}
+
+void DictTrie::free_resource(bool free_dict_list) {
+  if (NULL != root_)
+    free(root_);
+  root_ = NULL;
+
+  if (NULL != splid_le0_index_)
+    free(splid_le0_index_);
+  splid_le0_index_ = NULL;
+
+  if (NULL != nodes_ge1_)
+    free(nodes_ge1_);
+  nodes_ge1_ = NULL;
+
+  if (NULL != nodes_ge1_)
+    free(nodes_ge1_);
+  nodes_ge1_ = NULL;
+
+  if (free_dict_list) {
+    if (NULL != dict_list_) {
+      delete dict_list_;
+    }
+    dict_list_ = NULL;
+  }
+
+  if (parsing_marks_)
+    delete [] parsing_marks_;
+  parsing_marks_ = NULL;
+
+  if (mile_stones_)
+    delete [] mile_stones_;
+  mile_stones_ = NULL;
+
+  reset_milestones(0, kFirstValidMileStoneHandle);
+}
+
+inline size_t DictTrie::get_son_offset(const LmaNodeGE1 *node) {
+  return ((size_t)node->son_1st_off_l + ((size_t)node->son_1st_off_h << 16));
+}
+
+inline size_t DictTrie::get_homo_idx_buf_offset(const LmaNodeGE1 *node) {
+  return ((size_t)node->homo_idx_buf_off_l +
+          ((size_t)node->homo_idx_buf_off_h << 16));
+}
+
+inline LemmaIdType DictTrie::get_lemma_id(size_t id_offset) {
+  LemmaIdType id = 0;
+  for (uint16 pos = kLemmaIdSize - 1; pos > 0; pos--)
+    id = (id << 8) + lma_idx_buf_[id_offset * kLemmaIdSize + pos];
+  id = (id << 8) + lma_idx_buf_[id_offset * kLemmaIdSize];
+  return id;
+}
+
+#ifdef ___BUILD_MODEL___
+bool DictTrie::build_dict(const char* fn_raw, const char* fn_validhzs) {
+  DictBuilder* dict_builder = new DictBuilder();
+
+  free_resource(true);
+
+  return dict_builder->build_dict(fn_raw, fn_validhzs, this);
+}
+
+bool DictTrie::save_dict(FILE *fp) {
+  if (NULL == fp)
+    return false;
+
+  if (fwrite(&lma_node_num_le0_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fwrite(&lma_node_num_ge1_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fwrite(&lma_idx_buf_len_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fwrite(&top_lmas_num_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fwrite(root_, sizeof(LmaNodeLE0), lma_node_num_le0_, fp)
+      != lma_node_num_le0_)
+    return false;
+
+  if (fwrite(nodes_ge1_, sizeof(LmaNodeGE1), lma_node_num_ge1_, fp)
+      != lma_node_num_ge1_)
+    return false;
+
+  if (fwrite(lma_idx_buf_, sizeof(unsigned char), lma_idx_buf_len_, fp) !=
+      lma_idx_buf_len_)
+    return false;
+
+  return true;
+}
+
+bool DictTrie::save_dict(const char *filename) {
+  if (NULL == filename)
+    return false;
+
+  if (NULL == root_ || NULL == dict_list_)
+    return false;
+
+  SpellingTrie &spl_trie = SpellingTrie::get_instance();
+  NGram &ngram = NGram::get_instance();
+
+  FILE *fp = fopen(filename, "wb");
+  if (NULL == fp)
+    return false;
+
+  if (!spl_trie.save_spl_trie(fp) || !dict_list_->save_list(fp) ||
+      !save_dict(fp) || !ngram.save_ngram(fp)) {
+    fclose(fp);
+    return false;
+  }
+
+  fclose(fp);
+  return true;
+}
+#endif  // ___BUILD_MODEL___
+
+bool DictTrie::load_dict(FILE *fp) {
+  if (NULL == fp)
+    return false;
+
+  if (fread(&lma_node_num_le0_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fread(&lma_node_num_ge1_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fread(&lma_idx_buf_len_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fread(&top_lmas_num_, sizeof(size_t), 1, fp) != 1 ||
+      top_lmas_num_ >= lma_idx_buf_len_)
+    return false;
+
+  free_resource(false);
+
+  root_ = static_cast<LmaNodeLE0*>
+          (malloc(lma_node_num_le0_ * sizeof(LmaNodeLE0)));
+  nodes_ge1_ = static_cast<LmaNodeGE1*>
+               (malloc(lma_node_num_ge1_ * sizeof(LmaNodeGE1)));
+  lma_idx_buf_ = (unsigned char*)malloc(lma_idx_buf_len_);
+  total_lma_num_ = lma_idx_buf_len_ / kLemmaIdSize;
+
+  size_t buf_size = SpellingTrie::get_instance().get_spelling_num() + 1;
+  assert(lma_node_num_le0_ <= buf_size);
+  splid_le0_index_ = static_cast<uint16*>(malloc(buf_size * sizeof(uint16)));
+
+  // Init the space for parsing.
+  parsing_marks_ = new ParsingMark[kMaxParsingMark];
+  mile_stones_ = new MileStone[kMaxMileStone];
+  reset_milestones(0, kFirstValidMileStoneHandle);
+
+  if (NULL == root_ || NULL == nodes_ge1_ || NULL == lma_idx_buf_ ||
+      NULL == splid_le0_index_ || NULL == parsing_marks_ ||
+      NULL == mile_stones_) {
+    free_resource(false);
+    return false;
+  }
+
+  if (fread(root_, sizeof(LmaNodeLE0), lma_node_num_le0_, fp)
+      != lma_node_num_le0_)
+    return false;
+
+  if (fread(nodes_ge1_, sizeof(LmaNodeGE1), lma_node_num_ge1_, fp)
+      != lma_node_num_ge1_)
+    return false;
+
+  if (fread(lma_idx_buf_, sizeof(unsigned char), lma_idx_buf_len_, fp) !=
+      lma_idx_buf_len_)
+    return false;
+
+  // The quick index for the first level sons
+  uint16 last_splid = kFullSplIdStart;
+  size_t last_pos = 0;
+  for (size_t i = 1; i < lma_node_num_le0_; i++) {
+    for (uint16 splid = last_splid; splid < root_[i].spl_idx; splid++)
+      splid_le0_index_[splid - kFullSplIdStart] = last_pos;
+
+    splid_le0_index_[root_[i].spl_idx - kFullSplIdStart] =
+        static_cast<uint16>(i);
+    last_splid = root_[i].spl_idx;
+    last_pos = i;
+  }
+
+  for (uint16 splid = last_splid + 1;
+       splid < buf_size + kFullSplIdStart; splid++) {
+    assert(static_cast<size_t>(splid - kFullSplIdStart) < buf_size);
+    splid_le0_index_[splid - kFullSplIdStart] = last_pos + 1;
+  }
+
+  return true;
+}
+
+bool DictTrie::load_dict(const char *filename, LemmaIdType start_id,
+                         LemmaIdType end_id) {
+  if (NULL == filename || end_id <= start_id)
+    return false;
+
+  FILE *fp = fopen(filename, "rb");
+  if (NULL == fp)
+    return false;
+
+  free_resource(true);
+
+  dict_list_ = new DictList();
+  if (NULL == dict_list_) {
+    fclose(fp);
+    return false;
+  }
+
+  SpellingTrie &spl_trie = SpellingTrie::get_instance();
+  NGram &ngram = NGram::get_instance();
+
+  if (!spl_trie.load_spl_trie(fp) || !dict_list_->load_list(fp) ||
+      !load_dict(fp) || !ngram.load_ngram(fp) ||
+      total_lma_num_ > end_id - start_id + 1) {
+    free_resource(true);
+    fclose(fp);
+    return false;
+  }
+
+  fclose(fp);
+  return true;
+}
+
+bool DictTrie::load_dict_fd(int sys_fd, long start_offset,
+                            long length, LemmaIdType start_id,
+                            LemmaIdType end_id) {
+  if (start_offset < 0 || length <= 0 || end_id <= start_id)
+    return false;
+
+  FILE *fp = fdopen(sys_fd, "rb");
+  if (NULL == fp)
+    return false;
+
+  if (-1 == fseek(fp, start_offset, SEEK_SET)) {
+    fclose(fp);
+    return false;
+  }
+
+  free_resource(true);
+
+  dict_list_ = new DictList();
+  if (NULL == dict_list_) {
+    fclose(fp);
+    return false;
+  }
+
+  SpellingTrie &spl_trie = SpellingTrie::get_instance();
+  NGram &ngram = NGram::get_instance();
+
+  if (!spl_trie.load_spl_trie(fp) || !dict_list_->load_list(fp) ||
+      !load_dict(fp) || !ngram.load_ngram(fp) ||
+      ftell(fp) < start_offset + length ||
+      total_lma_num_ > end_id - start_id + 1) {
+    free_resource(true);
+    fclose(fp);
+    return false;
+  }
+
+  fclose(fp);
+  return true;
+}
+
+size_t DictTrie::fill_lpi_buffer(LmaPsbItem lpi_items[], size_t lpi_max,
+                                 LmaNodeLE0 *node) {
+  size_t lpi_num = 0;
+  NGram& ngram = NGram::get_instance();
+  for (size_t homo = 0; homo < (size_t)node->num_of_homo; homo++) {
+    lpi_items[lpi_num].id = get_lemma_id(node->homo_idx_buf_off +
+                                         homo);
+    lpi_items[lpi_num].lma_len = 1;
+    lpi_items[lpi_num].psb =
+        static_cast<LmaScoreType>(ngram.get_uni_psb(lpi_items[lpi_num].id));
+    lpi_num++;
+    if (lpi_num >= lpi_max)
+      break;
+  }
+
+  return lpi_num;
+}
+
+size_t DictTrie::fill_lpi_buffer(LmaPsbItem lpi_items[], size_t lpi_max,
+                                 size_t homo_buf_off, LmaNodeGE1 *node,
+                                 uint16 lma_len) {
+  size_t lpi_num = 0;
+  NGram& ngram = NGram::get_instance();
+  for (size_t homo = 0; homo < (size_t)node->num_of_homo; homo++) {
+    lpi_items[lpi_num].id = get_lemma_id(homo_buf_off + homo);
+    lpi_items[lpi_num].lma_len = lma_len;
+    lpi_items[lpi_num].psb =
+        static_cast<LmaScoreType>(ngram.get_uni_psb(lpi_items[lpi_num].id));
+    lpi_num++;
+    if (lpi_num >= lpi_max)
+      break;
+  }
+
+  return lpi_num;
+}
+
+void DictTrie::reset_milestones(uint16 from_step, MileStoneHandle from_handle) {
+  if (0 == from_step) {
+    parsing_marks_pos_ = 0;
+    mile_stones_pos_ = kFirstValidMileStoneHandle;
+  } else {
+    if (from_handle > 0 && from_handle < mile_stones_pos_) {
+      mile_stones_pos_ = from_handle;
+
+      MileStone *mile_stone = mile_stones_ + from_handle;
+      parsing_marks_pos_ = mile_stone->mark_start;
+    }
+  }
+}
+
+MileStoneHandle DictTrie::extend_dict(MileStoneHandle from_handle,
+                                      const DictExtPara *dep,
+                                      LmaPsbItem *lpi_items, size_t lpi_max,
+                                      size_t *lpi_num) {
+  if (NULL == dep)
+    return 0;
+
+  // from LmaNodeLE0 (root) to LmaNodeLE0
+  if (0 == from_handle) {
+    assert(0 == dep->splids_extended);
+    return extend_dict0(from_handle, dep, lpi_items, lpi_max, lpi_num);
+  }
+
+  // from LmaNodeLE0 to LmaNodeGE1
+  if (1 == dep->splids_extended)
+    return extend_dict1(from_handle, dep, lpi_items, lpi_max, lpi_num);
+
+  // From LmaNodeGE1 to LmaNodeGE1
+  return extend_dict2(from_handle, dep, lpi_items, lpi_max, lpi_num);
+}
+
+MileStoneHandle DictTrie::extend_dict0(MileStoneHandle from_handle,
+                                       const DictExtPara *dep,
+                                       LmaPsbItem *lpi_items,
+                                       size_t lpi_max, size_t *lpi_num) {
+  assert(NULL != dep && 0 == from_handle);
+  *lpi_num = 0;
+  MileStoneHandle ret_handle = 0;
+
+  uint16 splid = dep->splids[dep->splids_extended];
+  uint16 id_start = dep->id_start;
+  uint16 id_num = dep->id_num;
+
+  LpiCache& lpi_cache = LpiCache::get_instance();
+  bool cached = lpi_cache.is_cached(splid);
+
+  // 2. Begin exgtending
+  // 2.1 Get the LmaPsbItem list
+  LmaNodeLE0 *node = root_;
+  size_t son_start = splid_le0_index_[id_start - kFullSplIdStart];
+  size_t son_end = splid_le0_index_[id_start + id_num - kFullSplIdStart];
+  for (size_t son_pos = son_start; son_pos < son_end; son_pos++) {
+    assert(1 == node->son_1st_off);
+    LmaNodeLE0 *son = root_ + son_pos;
+    assert(son->spl_idx >= id_start && son->spl_idx < id_start + id_num);
+
+    if (!cached && *lpi_num < lpi_max) {
+      bool need_lpi = true;
+      if (spl_trie_->is_half_id_yunmu(splid) && son_pos != son_start)
+        need_lpi = false;
+
+      if (need_lpi)
+        *lpi_num += fill_lpi_buffer(lpi_items + (*lpi_num),
+                                    lpi_max - *lpi_num, son);
+    }
+
+    // If necessary, fill in a new mile stone.
+    if (son->spl_idx == id_start) {
+      if (mile_stones_pos_ < kMaxMileStone &&
+          parsing_marks_pos_ < kMaxParsingMark) {
+        parsing_marks_[parsing_marks_pos_].node_offset = son_pos;
+        parsing_marks_[parsing_marks_pos_].node_num = id_num;
+        mile_stones_[mile_stones_pos_].mark_start = parsing_marks_pos_;
+        mile_stones_[mile_stones_pos_].mark_num = 1;
+        ret_handle = mile_stones_pos_;
+        parsing_marks_pos_++;
+        mile_stones_pos_++;
+      }
+    }
+
+    if (son->spl_idx >= id_start + id_num -1)
+      break;
+  }
+
+  //  printf("----- parsing marks: %d, mile stone: %d \n", parsing_marks_pos_,
+  //      mile_stones_pos_);
+  return ret_handle;
+}
+
+MileStoneHandle DictTrie::extend_dict1(MileStoneHandle from_handle,
+                                       const DictExtPara *dep,
+                                       LmaPsbItem *lpi_items,
+                                       size_t lpi_max, size_t *lpi_num) {
+  assert(NULL != dep && from_handle > 0 && from_handle < mile_stones_pos_);
+
+  MileStoneHandle ret_handle = 0;
+
+  // 1. If this is a half Id, get its corresponding full starting Id and
+  // number of full Id.
+  size_t ret_val = 0;
+
+  uint16 id_start = dep->id_start;
+  uint16 id_num = dep->id_num;
+
+  // 2. Begin extending.
+  MileStone *mile_stone = mile_stones_ + from_handle;
+
+  for (uint16 h_pos = 0; h_pos < mile_stone->mark_num; h_pos++) {
+    ParsingMark p_mark = parsing_marks_[mile_stone->mark_start + h_pos];
+    uint16 ext_num = p_mark.node_num;
+    for (uint16 ext_pos = 0; ext_pos < ext_num; ext_pos++) {
+      LmaNodeLE0 *node = root_ + p_mark.node_offset + ext_pos;
+      size_t found_start = 0;
+      size_t found_num = 0;
+      for (size_t son_pos = 0; son_pos < (size_t)node->num_of_son; son_pos++) {
+        assert(node->son_1st_off <= lma_node_num_ge1_);
+        LmaNodeGE1 *son = nodes_ge1_ + node->son_1st_off + son_pos;
+        if (son->spl_idx >= id_start
+            && son->spl_idx < id_start + id_num) {
+          if (*lpi_num < lpi_max) {
+            size_t homo_buf_off = get_homo_idx_buf_offset(son);
+            *lpi_num += fill_lpi_buffer(lpi_items + (*lpi_num),
+                                        lpi_max - *lpi_num, homo_buf_off, son,
+                                        2);
+          }
+
+          // If necessary, fill in the new DTMI
+          if (0 == found_num) {
+            found_start = son_pos;
+          }
+          found_num++;
+        }
+        if (son->spl_idx >= id_start + id_num - 1 || son_pos ==
+            (size_t)node->num_of_son - 1) {
+          if (found_num > 0) {
+            if (mile_stones_pos_ < kMaxMileStone &&
+                parsing_marks_pos_ < kMaxParsingMark) {
+              parsing_marks_[parsing_marks_pos_].node_offset =
+                node->son_1st_off + found_start;
+              parsing_marks_[parsing_marks_pos_].node_num = found_num;
+              if (0 == ret_val)
+                mile_stones_[mile_stones_pos_].mark_start =
+                  parsing_marks_pos_;
+              parsing_marks_pos_++;
+            }
+
+            ret_val++;
+          }
+          break;
+        }  // for son_pos
+      }  // for ext_pos
+    }  // for h_pos
+  }
+
+  if (ret_val > 0) {
+    mile_stones_[mile_stones_pos_].mark_num = ret_val;
+    ret_handle = mile_stones_pos_;
+    mile_stones_pos_++;
+    ret_val = 1;
+  }
+
+  //  printf("----- parsing marks: %d, mile stone: %d \n", parsing_marks_pos_,
+  //         mile_stones_pos_);
+  return ret_handle;
+}
+
+MileStoneHandle DictTrie::extend_dict2(MileStoneHandle from_handle,
+                                       const DictExtPara *dep,
+                                       LmaPsbItem *lpi_items,
+                                       size_t lpi_max, size_t *lpi_num) {
+  assert(NULL != dep && from_handle > 0 && from_handle < mile_stones_pos_);
+
+  MileStoneHandle ret_handle = 0;
+
+  // 1. If this is a half Id, get its corresponding full starting Id and
+  // number of full Id.
+  size_t ret_val = 0;
+
+  uint16 id_start = dep->id_start;
+  uint16 id_num = dep->id_num;
+
+  // 2. Begin extending.
+  MileStone *mile_stone = mile_stones_ + from_handle;
+
+  for (uint16 h_pos = 0; h_pos < mile_stone->mark_num; h_pos++) {
+    ParsingMark p_mark = parsing_marks_[mile_stone->mark_start + h_pos];
+    uint16 ext_num = p_mark.node_num;
+    for (uint16 ext_pos = 0; ext_pos < ext_num; ext_pos++) {
+      LmaNodeGE1 *node = nodes_ge1_ + p_mark.node_offset + ext_pos;
+      size_t found_start = 0;
+      size_t found_num = 0;
+
+      for (size_t son_pos = 0; son_pos < (size_t)node->num_of_son; son_pos++) {
+        assert(node->son_1st_off_l > 0 || node->son_1st_off_h > 0);
+        LmaNodeGE1 *son = nodes_ge1_ + get_son_offset(node) + son_pos;
+        if (son->spl_idx >= id_start
+            && son->spl_idx < id_start + id_num) {
+          if (*lpi_num < lpi_max) {
+            size_t homo_buf_off = get_homo_idx_buf_offset(son);
+            *lpi_num += fill_lpi_buffer(lpi_items + (*lpi_num),
+                                        lpi_max - *lpi_num, homo_buf_off, son,
+                                        dep->splids_extended + 1);
+          }
+
+          // If necessary, fill in the new DTMI
+          if (0 == found_num) {
+            found_start = son_pos;
+          }
+          found_num++;
+        }
+        if (son->spl_idx >= id_start + id_num - 1 || son_pos ==
+            (size_t)node->num_of_son - 1) {
+          if (found_num > 0) {
+            if (mile_stones_pos_ < kMaxMileStone &&
+                parsing_marks_pos_ < kMaxParsingMark) {
+              parsing_marks_[parsing_marks_pos_].node_offset =
+                get_son_offset(node) + found_start;
+              parsing_marks_[parsing_marks_pos_].node_num = found_num;
+              if (0 == ret_val)
+                mile_stones_[mile_stones_pos_].mark_start =
+                  parsing_marks_pos_;
+              parsing_marks_pos_++;
+            }
+
+            ret_val++;
+          }
+          break;
+        }
+      }  // for son_pos
+    }  // for ext_pos
+  }  // for h_pos
+
+  if (ret_val > 0) {
+    mile_stones_[mile_stones_pos_].mark_num = ret_val;
+    ret_handle = mile_stones_pos_;
+    mile_stones_pos_++;
+  }
+
+  // printf("----- parsing marks: %d, mile stone: %d \n", parsing_marks_pos_,
+  //        mile_stones_pos_);
+  return ret_handle;
+}
+
+bool DictTrie::try_extend(const uint16 *splids, uint16 splid_num,
+                          LemmaIdType id_lemma) {
+  if (0 == splid_num || NULL == splids)
+    return false;
+
+  void *node = root_ + splid_le0_index_[splids[0] - kFullSplIdStart];
+
+  for (uint16 pos = 1; pos < splid_num; pos++) {
+    if (1 == pos) {
+      LmaNodeLE0 *node_le0 = reinterpret_cast<LmaNodeLE0*>(node);
+      LmaNodeGE1 *node_son;
+      uint16 son_pos;
+      for (son_pos = 0; son_pos < static_cast<uint16>(node_le0->num_of_son);
+           son_pos++) {
+        assert(node_le0->son_1st_off <= lma_node_num_ge1_);
+        node_son = nodes_ge1_ + node_le0->son_1st_off
+            + son_pos;
+        if (node_son->spl_idx == splids[pos])
+          break;
+      }
+      if (son_pos < node_le0->num_of_son)
+        node = reinterpret_cast<void*>(node_son);
+      else
+        return false;
+    } else {
+      LmaNodeGE1 *node_ge1 = reinterpret_cast<LmaNodeGE1*>(node);
+      LmaNodeGE1 *node_son;
+      uint16 son_pos;
+      for (son_pos = 0; son_pos < static_cast<uint16>(node_ge1->num_of_son);
+           son_pos++) {
+        assert(node_ge1->son_1st_off_l > 0 || node_ge1->son_1st_off_h > 0);
+        node_son = nodes_ge1_ + get_son_offset(node_ge1) + son_pos;
+        if (node_son->spl_idx == splids[pos])
+          break;
+      }
+      if (son_pos < node_ge1->num_of_son)
+        node = reinterpret_cast<void*>(node_son);
+      else
+        return false;
+    }
+  }
+
+  if (1 == splid_num) {
+    LmaNodeLE0* node_le0 = reinterpret_cast<LmaNodeLE0*>(node);
+    size_t num_of_homo = (size_t)node_le0->num_of_homo;
+    for (size_t homo_pos = 0; homo_pos < num_of_homo; homo_pos++) {
+      LemmaIdType id_this = get_lemma_id(node_le0->homo_idx_buf_off + homo_pos);
+      char16 str[2];
+      get_lemma_str(id_this, str, 2);
+      if (id_this == id_lemma)
+        return true;
+    }
+  } else {
+    LmaNodeGE1* node_ge1 = reinterpret_cast<LmaNodeGE1*>(node);
+    size_t num_of_homo = (size_t)node_ge1->num_of_homo;
+    for (size_t homo_pos = 0; homo_pos < num_of_homo; homo_pos++) {
+      size_t node_homo_off = get_homo_idx_buf_offset(node_ge1);
+      if (get_lemma_id(node_homo_off + homo_pos) == id_lemma)
+        return true;
+    }
+  }
+
+  return false;
+}
+
+size_t DictTrie::get_lpis(const uint16* splid_str, uint16 splid_str_len,
+                          LmaPsbItem* lma_buf, size_t max_lma_buf) {
+  if (splid_str_len > kMaxLemmaSize)
+    return 0;
+
+#define MAX_EXTENDBUF_LEN 200
+
+  size_t* node_buf1[MAX_EXTENDBUF_LEN];  // use size_t for data alignment
+  size_t* node_buf2[MAX_EXTENDBUF_LEN];
+  LmaNodeLE0** node_fr_le0 =
+    reinterpret_cast<LmaNodeLE0**>(node_buf1);      // Nodes from.
+  LmaNodeLE0** node_to_le0 =
+    reinterpret_cast<LmaNodeLE0**>(node_buf2);      // Nodes to.
+  LmaNodeGE1** node_fr_ge1 = NULL;
+  LmaNodeGE1** node_to_ge1 = NULL;
+  size_t node_fr_num = 1;
+  size_t node_to_num = 0;
+  node_fr_le0[0] = root_;
+  if (NULL == node_fr_le0[0])
+    return 0;
+
+  size_t spl_pos = 0;
+
+  while (spl_pos < splid_str_len) {
+    uint16 id_num = 1;
+    uint16 id_start = splid_str[spl_pos];
+    // If it is a half id
+    if (spl_trie_->is_half_id(splid_str[spl_pos])) {
+      id_num = spl_trie_->half_to_full(splid_str[spl_pos], &id_start);
+      assert(id_num > 0);
+    }
+
+    // Extend the nodes
+    if (0 == spl_pos) {  // From LmaNodeLE0 (root) to LmaNodeLE0 nodes
+      for (size_t node_fr_pos = 0; node_fr_pos < node_fr_num; node_fr_pos++) {
+        LmaNodeLE0 *node = node_fr_le0[node_fr_pos];
+        assert(node == root_ && 1 == node_fr_num);
+        size_t son_start = splid_le0_index_[id_start - kFullSplIdStart];
+        size_t son_end =
+            splid_le0_index_[id_start + id_num - kFullSplIdStart];
+        for (size_t son_pos = son_start; son_pos < son_end; son_pos++) {
+          assert(1 == node->son_1st_off);
+          LmaNodeLE0 *node_son = root_ + son_pos;
+          assert(node_son->spl_idx >= id_start
+                 && node_son->spl_idx < id_start + id_num);
+          if (node_to_num < MAX_EXTENDBUF_LEN) {
+            node_to_le0[node_to_num] = node_son;
+            node_to_num++;
+          }
+          // id_start + id_num - 1 is the last one, which has just been
+          // recorded.
+          if (node_son->spl_idx >= id_start + id_num - 1)
+            break;
+        }
+      }
+
+      spl_pos++;
+      if (spl_pos >= splid_str_len || node_to_num == 0)
+        break;
+      // Prepare the nodes for next extending
+      // next time, from LmaNodeLE0 to LmaNodeGE1
+      LmaNodeLE0** node_tmp = node_fr_le0;
+      node_fr_le0 = node_to_le0;
+      node_to_le0 = NULL;
+      node_to_ge1 = reinterpret_cast<LmaNodeGE1**>(node_tmp);
+    } else if (1 == spl_pos) {  // From LmaNodeLE0 to LmaNodeGE1 nodes
+      for (size_t node_fr_pos = 0; node_fr_pos < node_fr_num; node_fr_pos++) {
+        LmaNodeLE0 *node = node_fr_le0[node_fr_pos];
+        for (size_t son_pos = 0; son_pos < (size_t)node->num_of_son;
+             son_pos++) {
+          assert(node->son_1st_off <= lma_node_num_ge1_);
+          LmaNodeGE1 *node_son = nodes_ge1_ + node->son_1st_off
+                                  + son_pos;
+          if (node_son->spl_idx >= id_start
+              && node_son->spl_idx < id_start + id_num) {
+            if (node_to_num < MAX_EXTENDBUF_LEN) {
+              node_to_ge1[node_to_num] = node_son;
+              node_to_num++;
+            }
+          }
+          // id_start + id_num - 1 is the last one, which has just been
+          // recorded.
+          if (node_son->spl_idx >= id_start + id_num - 1)
+            break;
+        }
+      }
+
+      spl_pos++;
+      if (spl_pos >= splid_str_len || node_to_num == 0)
+        break;
+      // Prepare the nodes for next extending
+      // next time, from LmaNodeGE1 to LmaNodeGE1
+      node_fr_ge1 = node_to_ge1;
+      node_to_ge1 = reinterpret_cast<LmaNodeGE1**>(node_fr_le0);
+      node_fr_le0 = NULL;
+      node_to_le0 = NULL;
+    } else {  // From LmaNodeGE1 to LmaNodeGE1 nodes
+      for (size_t node_fr_pos = 0; node_fr_pos < node_fr_num; node_fr_pos++) {
+        LmaNodeGE1 *node = node_fr_ge1[node_fr_pos];
+        for (size_t son_pos = 0; son_pos < (size_t)node->num_of_son;
+             son_pos++) {
+          assert(node->son_1st_off_l > 0 || node->son_1st_off_h > 0);
+          LmaNodeGE1 *node_son = nodes_ge1_
+                                  + get_son_offset(node) + son_pos;
+          if (node_son->spl_idx >= id_start
+              && node_son->spl_idx < id_start + id_num) {
+            if (node_to_num < MAX_EXTENDBUF_LEN) {
+              node_to_ge1[node_to_num] = node_son;
+              node_to_num++;
+            }
+          }
+          // id_start + id_num - 1 is the last one, which has just been
+          // recorded.
+          if (node_son->spl_idx >= id_start + id_num - 1)
+            break;
+        }
+      }
+
+      spl_pos++;
+      if (spl_pos >= splid_str_len || node_to_num == 0)
+        break;
+      // Prepare the nodes for next extending
+      // next time, from LmaNodeGE1 to LmaNodeGE1
+      LmaNodeGE1 **node_tmp = node_fr_ge1;
+      node_fr_ge1 = node_to_ge1;
+      node_to_ge1 = node_tmp;
+    }
+
+    // The number of node for next extending
+    node_fr_num = node_to_num;
+    node_to_num = 0;
+  }  // while
+
+  if (0 == node_to_num)
+    return 0;
+
+  NGram &ngram = NGram::get_instance();
+  size_t lma_num = 0;
+
+  // If the length is 1, and the splid is a one-char Yunmu like 'a', 'o', 'e',
+  // only those candidates for the full matched one-char id will be returned.
+  if (1 == splid_str_len && spl_trie_->is_half_id_yunmu(splid_str[0]))
+    node_to_num = node_to_num > 0 ? 1 : 0;
+
+  for (size_t node_pos = 0; node_pos < node_to_num; node_pos++) {
+    size_t num_of_homo = 0;
+    if (spl_pos <= 1) {  // Get from LmaNodeLE0 nodes
+      LmaNodeLE0* node_le0 = node_to_le0[node_pos];
+      num_of_homo = (size_t)node_le0->num_of_homo;
+      for (size_t homo_pos = 0; homo_pos < num_of_homo; homo_pos++) {
+        size_t ch_pos = lma_num + homo_pos;
+        lma_buf[ch_pos].id =
+            get_lemma_id(node_le0->homo_idx_buf_off + homo_pos);
+        lma_buf[ch_pos].lma_len = 1;
+        lma_buf[ch_pos].psb =
+            static_cast<LmaScoreType>(ngram.get_uni_psb(lma_buf[ch_pos].id));
+
+        if (lma_num + homo_pos >= max_lma_buf - 1)
+          break;
+      }
+    } else {  // Get from LmaNodeGE1 nodes
+      LmaNodeGE1* node_ge1 = node_to_ge1[node_pos];
+      num_of_homo = (size_t)node_ge1->num_of_homo;
+      for (size_t homo_pos = 0; homo_pos < num_of_homo; homo_pos++) {
+        size_t ch_pos = lma_num + homo_pos;
+        size_t node_homo_off = get_homo_idx_buf_offset(node_ge1);
+        lma_buf[ch_pos].id = get_lemma_id(node_homo_off + homo_pos);
+        lma_buf[ch_pos].lma_len = splid_str_len;
+        lma_buf[ch_pos].psb =
+            static_cast<LmaScoreType>(ngram.get_uni_psb(lma_buf[ch_pos].id));
+
+        if (lma_num + homo_pos >= max_lma_buf - 1)
+          break;
+      }
+    }
+
+    lma_num += num_of_homo;
+    if (lma_num >= max_lma_buf) {
+      lma_num = max_lma_buf;
+      break;
+    }
+  }
+  return lma_num;
+}
+
+uint16 DictTrie::get_lemma_str(LemmaIdType id_lemma, char16 *str_buf,
+                               uint16 str_max) {
+  return dict_list_->get_lemma_str(id_lemma, str_buf, str_max);
+}
+
+uint16 DictTrie::get_lemma_splids(LemmaIdType id_lemma, uint16 *splids,
+                                  uint16 splids_max, bool arg_valid) {
+  char16 lma_str[kMaxLemmaSize + 1];
+  uint16 lma_len = get_lemma_str(id_lemma, lma_str, kMaxLemmaSize + 1);
+  assert((!arg_valid && splids_max >= lma_len) || lma_len == splids_max);
+
+  uint16 spl_mtrx[kMaxLemmaSize * 5];
+  uint16 spl_start[kMaxLemmaSize + 1];
+  spl_start[0] = 0;
+  uint16 try_num = 1;
+
+  for (uint16 pos = 0; pos < lma_len; pos++) {
+    uint16 cand_splids_this = 0;
+    if (arg_valid && spl_trie_->is_full_id(splids[pos])) {
+      spl_mtrx[spl_start[pos]] = splids[pos];
+      cand_splids_this = 1;
+    } else {
+      cand_splids_this = dict_list_->get_splids_for_hanzi(lma_str[pos],
+          arg_valid ? splids[pos] : 0, spl_mtrx + spl_start[pos],
+          kMaxLemmaSize * 5 - spl_start[pos]);
+      assert(cand_splids_this > 0);
+    }
+    spl_start[pos + 1] = spl_start[pos] + cand_splids_this;
+    try_num *= cand_splids_this;
+  }
+
+  for (uint16 try_pos = 0; try_pos < try_num; try_pos++) {
+    uint16 mod = 1;
+    for (uint16 pos = 0; pos < lma_len; pos++) {
+      uint16 radix = spl_start[pos + 1] - spl_start[pos];
+      splids[pos] = spl_mtrx[ spl_start[pos] + try_pos / mod % radix];
+      mod *= radix;
+    }
+
+    if (try_extend(splids, lma_len, id_lemma))
+      return lma_len;
+  }
+
+  return 0;
+}
+
+void DictTrie::set_total_lemma_count_of_others(size_t count) {
+  NGram& ngram = NGram::get_instance();
+  ngram.set_total_freq_none_sys(count);
+}
+
+void DictTrie::convert_to_hanzis(char16 *str, uint16 str_len) {
+  return dict_list_->convert_to_hanzis(str, str_len);
+}
+
+void DictTrie::convert_to_scis_ids(char16 *str, uint16 str_len) {
+  return dict_list_->convert_to_scis_ids(str, str_len);
+}
+
+LemmaIdType DictTrie::get_lemma_id(const char16 lemma_str[], uint16 lemma_len) {
+  if (NULL == lemma_str || lemma_len > kMaxLemmaSize)
+    return 0;
+
+  return dict_list_->get_lemma_id(lemma_str, lemma_len);
+}
+
+size_t DictTrie::predict_top_lmas(size_t his_len, NPredictItem *npre_items,
+                                  size_t npre_max, size_t b4_used) {
+  NGram &ngram = NGram::get_instance();
+
+  size_t item_num = 0;
+  size_t top_lmas_id_offset = lma_idx_buf_len_ / kLemmaIdSize - top_lmas_num_;
+  size_t top_lmas_pos = 0;
+  while (item_num < npre_max && top_lmas_pos < top_lmas_num_) {
+    memset(npre_items + item_num, 0, sizeof(NPredictItem));
+    LemmaIdType top_lma_id = get_lemma_id(top_lmas_id_offset + top_lmas_pos);
+    top_lmas_pos += 1;
+    if (dict_list_->get_lemma_str(top_lma_id,
+                                  npre_items[item_num].pre_hzs,
+                                  kMaxLemmaSize - 1) == 0) {
+      continue;
+    }
+    npre_items[item_num].psb = ngram.get_uni_psb(top_lma_id);
+    npre_items[item_num].his_len = his_len;
+    item_num++;
+  }
+  return item_num;
+}
+
+size_t DictTrie::predict(const char16 *last_hzs, uint16 hzs_len,
+                         NPredictItem *npre_items, size_t npre_max,
+                         size_t b4_used) {
+  return dict_list_->predict(last_hzs, hzs_len, npre_items, npre_max, b4_used);
+}
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/lpicache.cpp b/PinyinIME/jni/share/lpicache.cpp
new file mode 100644
index 0000000..4bb4ca2
--- /dev/null
+++ b/PinyinIME/jni/share/lpicache.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include "../include/lpicache.h"
+
+namespace ime_pinyin {
+
+LpiCache* LpiCache::instance_ = NULL;
+
+LpiCache::LpiCache() {
+  lpi_cache_ = new LmaPsbItem[kFullSplIdStart * kMaxLpiCachePerId];
+  lpi_cache_len_ = new uint16[kFullSplIdStart];
+  assert(NULL != lpi_cache_);
+  assert(NULL != lpi_cache_len_);
+  for (uint16 id = 0; id < kFullSplIdStart; id++)
+    lpi_cache_len_[id] = 0;
+}
+
+LpiCache::~LpiCache() {
+  if (NULL != lpi_cache_)
+    delete [] lpi_cache_;
+
+  if (NULL != lpi_cache_len_)
+    delete [] lpi_cache_len_;
+}
+
+LpiCache& LpiCache::get_instance() {
+  if (NULL == instance_) {
+    instance_ = new LpiCache();
+    assert(NULL != instance_);
+  }
+  return *instance_;
+}
+
+bool LpiCache::is_cached(uint16 splid) {
+  if (splid >= kFullSplIdStart)
+    return false;
+  return lpi_cache_len_[splid] != 0;
+}
+
+size_t LpiCache::put_cache(uint16 splid, LmaPsbItem lpi_items[],
+                           size_t lpi_num) {
+  uint16 num = kMaxLpiCachePerId;
+  if (num > lpi_num)
+    num = static_cast<uint16>(lpi_num);
+
+  LmaPsbItem *lpi_cache_this = lpi_cache_ + splid * kMaxLpiCachePerId;
+  for (uint16 pos = 0; pos < num; pos++)
+    lpi_cache_this[pos] = lpi_items[pos];
+
+  lpi_cache_len_[splid] = num;
+  return num;
+}
+
+size_t LpiCache::get_cache(uint16 splid, LmaPsbItem lpi_items[],
+                           size_t lpi_max) {
+  if (lpi_max > lpi_cache_len_[splid])
+    lpi_max = lpi_cache_len_[splid];
+
+  LmaPsbItem *lpi_cache_this = lpi_cache_ + splid * kMaxLpiCachePerId;
+  for (uint16 pos = 0; pos < lpi_max; pos++) {
+    lpi_items[pos] = lpi_cache_this[pos];
+  }
+  return lpi_max;
+}
+
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/matrixsearch.cpp b/PinyinIME/jni/share/matrixsearch.cpp
new file mode 100644
index 0000000..d13a4c4
--- /dev/null
+++ b/PinyinIME/jni/share/matrixsearch.cpp
@@ -0,0 +1,1895 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include "../include/lpicache.h"
+#include "../include/matrixsearch.h"
+#include "../include/mystdlib.h"
+#include "../include/ngram.h"
+#include "../include/userdict.h"
+
+namespace ime_pinyin {
+
+#define PRUMING_SCORE 8000.0
+
+MatrixSearch::MatrixSearch() {
+  inited_ = false;
+  spl_trie_ = SpellingTrie::get_cpinstance();
+
+  reset_pointers_to_null();
+
+  pys_decoded_len_ = 0;
+  mtrx_nd_pool_used_ = 0;
+  dmi_pool_used_ = 0;
+  xi_an_enabled_ = false;
+  dmi_c_phrase_ = false;
+
+  assert(kMaxSearchSteps > 0);
+  max_sps_len_ = kMaxSearchSteps - 1;
+  max_hzs_len_ = kMaxSearchSteps;
+}
+
+MatrixSearch::~MatrixSearch() {
+  free_resource();
+}
+
+void MatrixSearch::reset_pointers_to_null() {
+  dict_trie_ = NULL;
+  user_dict_ = NULL;
+  spl_parser_ = NULL;
+
+  share_buf_ = NULL;
+
+  // The following four buffers are used for decoding, and they are based on
+  // share_buf_, no need to delete them.
+  mtrx_nd_pool_ = NULL;
+  dmi_pool_ = NULL;
+  matrix_ = NULL;
+  dep_ = NULL;
+
+  // Based on share_buf_, no need to delete them.
+  npre_items_ = NULL;
+}
+
+bool MatrixSearch::alloc_resource() {
+  free_resource();
+
+  dict_trie_ = new DictTrie();
+  user_dict_ = static_cast<AtomDictBase*>(new UserDict());
+  spl_parser_ = new SpellingParser();
+
+  size_t mtrx_nd_size = sizeof(MatrixNode) * kMtrxNdPoolSize;
+  mtrx_nd_size = align_to_size_t(mtrx_nd_size) / sizeof(size_t);
+  size_t dmi_size = sizeof(DictMatchInfo) * kDmiPoolSize;
+  dmi_size = align_to_size_t(dmi_size) / sizeof(size_t);
+  size_t matrix_size = sizeof(MatrixRow) * kMaxRowNum;
+  matrix_size = align_to_size_t(matrix_size) / sizeof(size_t);
+  size_t dep_size = sizeof(DictExtPara);
+  dep_size = align_to_size_t(dep_size) / sizeof(size_t);
+
+  // share_buf's size is determined by the buffers for search.
+  share_buf_ = new size_t[mtrx_nd_size + dmi_size + matrix_size + dep_size];
+
+  if (NULL == dict_trie_ || NULL == user_dict_ || NULL == spl_parser_ ||
+      NULL == share_buf_)
+    return false;
+
+  // The buffers for search are based on the share buffer
+  mtrx_nd_pool_ = reinterpret_cast<MatrixNode*>(share_buf_);
+  dmi_pool_ = reinterpret_cast<DictMatchInfo*>(share_buf_ + mtrx_nd_size);
+  matrix_ = reinterpret_cast<MatrixRow*>(share_buf_ + mtrx_nd_size + dmi_size);
+  dep_ = reinterpret_cast<DictExtPara*>
+      (share_buf_ + mtrx_nd_size + dmi_size + matrix_size);
+
+  // The prediction buffer is also based on the share buffer.
+  npre_items_ = reinterpret_cast<NPredictItem*>(share_buf_);
+  npre_items_len_ = (mtrx_nd_size + dmi_size + matrix_size + dep_size) *
+      sizeof(size_t) / sizeof(NPredictItem);
+  return true;
+}
+
+void MatrixSearch::free_resource() {
+  if (NULL != dict_trie_)
+    delete dict_trie_;
+
+  if (NULL != user_dict_)
+    delete user_dict_;
+
+  if (NULL != spl_parser_)
+    delete spl_parser_;
+
+  if (NULL != share_buf_)
+    delete [] share_buf_;
+
+  reset_pointers_to_null();
+}
+
+bool MatrixSearch::init(const char *fn_sys_dict, const char *fn_usr_dict) {
+  if (NULL == fn_sys_dict || NULL == fn_usr_dict)
+    return false;
+
+  if (!alloc_resource())
+    return false;
+
+  if (!dict_trie_->load_dict(fn_sys_dict, 1, kSysDictIdEnd))
+    return false;
+
+  // If engine fails to load the user dictionary, reset the user dictionary
+  // to NULL.
+  if (!user_dict_->load_dict(fn_usr_dict, kUserDictIdStart, kUserDictIdEnd)) {
+    delete user_dict_;
+    user_dict_ = NULL;
+  } else{
+    user_dict_->set_total_lemma_count_of_others(NGram::kSysDictTotalFreq);
+  }
+
+  reset_search(0, true, true, true);
+
+  inited_ = true;
+  return true;
+}
+
+bool MatrixSearch::init_fd(int sys_fd, long start_offset, long length,
+                           const char *fn_usr_dict) {
+  if (NULL == fn_usr_dict)
+    return false;
+
+  if (!alloc_resource())
+    return false;
+
+  if (!dict_trie_->load_dict_fd(sys_fd, start_offset, length, 1, kSysDictIdEnd))
+    return false;
+
+  if (!user_dict_->load_dict(fn_usr_dict, kUserDictIdStart, kUserDictIdEnd)) {
+    delete user_dict_;
+    user_dict_ = NULL;
+  } else {
+    user_dict_->set_total_lemma_count_of_others(NGram::kSysDictTotalFreq);
+  }
+
+  reset_search(0, true, true, true);
+
+  inited_ = true;
+  return true;
+}
+
+void MatrixSearch::set_max_lens(size_t max_sps_len, size_t max_hzs_len) {
+  if (0 != max_sps_len)
+    max_sps_len_ = max_sps_len;
+  if (0 != max_hzs_len)
+    max_hzs_len_ = max_hzs_len;
+}
+
+void MatrixSearch::close() {
+  flush_cache();
+  free_resource();
+  inited_ = false;
+}
+
+void MatrixSearch::flush_cache() {
+  if (NULL != user_dict_)
+    user_dict_->flush_cache();
+}
+
+void MatrixSearch::set_xi_an_switch(bool xi_an_enabled) {
+  xi_an_enabled_ = xi_an_enabled;
+}
+
+bool MatrixSearch::get_xi_an_switch() {
+  return xi_an_enabled_;
+}
+
+bool MatrixSearch::reset_search() {
+  if (!inited_)
+    return false;
+  return reset_search(0, true, true, true);
+}
+
+bool MatrixSearch::reset_search(size_t ch_pos, bool clear_fixed_this_step,
+                                bool clear_dmi_this_step,
+                                bool clear_mtrx_this_step) {
+  if (!inited_ || ch_pos > pys_decoded_len_ || ch_pos >= kMaxRowNum)
+    return false;
+
+  if (0 == ch_pos) {
+    pys_decoded_len_ = 0;
+    mtrx_nd_pool_used_ = 0;
+    dmi_pool_used_ = 0;
+
+    // Get a MatrixNode from the pool
+    matrix_[0].mtrx_nd_pos = mtrx_nd_pool_used_;
+    matrix_[0].mtrx_nd_num = 1;
+    mtrx_nd_pool_used_ += 1;
+
+    // Update the node, and make it to be a starting node
+    MatrixNode *node = mtrx_nd_pool_ + matrix_[0].mtrx_nd_pos;
+    node->id = 0;
+    node->score = 0;
+    node->from = NULL;
+    node->step = 0;
+    node->dmi_fr = (PoolPosType)-1;
+
+    matrix_[0].dmi_pos = 0;
+    matrix_[0].dmi_num = 0;
+    matrix_[0].dmi_has_full_id = 1;
+    matrix_[0].mtrx_nd_fixed = node;
+
+    lma_start_[0] = 0;
+    fixed_lmas_ = 0;
+    spl_start_[0] = 0;
+    fixed_hzs_ = 0;
+
+    dict_trie_->reset_milestones(0, 0);
+    if (NULL != user_dict_)
+      user_dict_->reset_milestones(0, 0);
+  } else {
+    // Prepare mile stones of this step to clear.
+    MileStoneHandle *dict_handles_to_clear = NULL;
+    if (clear_dmi_this_step && matrix_[ch_pos].dmi_num > 0) {
+      dict_handles_to_clear = dmi_pool_[matrix_[ch_pos].dmi_pos].dict_handles;
+    }
+
+    // If there are more steps, and this step is not allowed to clear, find
+    // milestones of next step.
+    if (pys_decoded_len_ > ch_pos && !clear_dmi_this_step) {
+      dict_handles_to_clear = NULL;
+      if (matrix_[ch_pos + 1].dmi_num > 0) {
+        dict_handles_to_clear =
+            dmi_pool_[matrix_[ch_pos + 1].dmi_pos].dict_handles;
+      }
+    }
+
+    if (NULL != dict_handles_to_clear) {
+      dict_trie_->reset_milestones(ch_pos, dict_handles_to_clear[0]);
+      if (NULL != user_dict_)
+        user_dict_->reset_milestones(ch_pos, dict_handles_to_clear[1]);
+    }
+
+    pys_decoded_len_ = ch_pos;
+
+    if (clear_dmi_this_step) {
+      dmi_pool_used_ = matrix_[ch_pos - 1].dmi_pos
+                       + matrix_[ch_pos - 1].dmi_num;
+      matrix_[ch_pos].dmi_num = 0;
+    } else {
+      dmi_pool_used_ = matrix_[ch_pos].dmi_pos + matrix_[ch_pos].dmi_num;
+    }
+
+    if (clear_mtrx_this_step) {
+      mtrx_nd_pool_used_ = matrix_[ch_pos - 1].mtrx_nd_pos
+                           + matrix_[ch_pos - 1].mtrx_nd_num;
+      matrix_[ch_pos].mtrx_nd_num = 0;
+    } else {
+      mtrx_nd_pool_used_ = matrix_[ch_pos].mtrx_nd_pos
+                           + matrix_[ch_pos].mtrx_nd_num;
+    }
+
+    // Modify fixed_hzs_
+    if (fixed_hzs_ > 0 && kLemmaIdComposing != lma_id_[0]) {
+      size_t fixed_ch_pos = ch_pos;
+      if (clear_fixed_this_step)
+        fixed_ch_pos = fixed_ch_pos > 0 ? fixed_ch_pos - 1 : 0;
+      while (NULL == matrix_[fixed_ch_pos].mtrx_nd_fixed && fixed_ch_pos > 0)
+        fixed_ch_pos--;
+
+      fixed_lmas_ = 0;
+      fixed_hzs_ = 0;
+      if (fixed_ch_pos > 0) {
+        while (spl_start_[fixed_hzs_] < fixed_ch_pos)
+          fixed_hzs_++;
+        assert(spl_start_[fixed_hzs_] == fixed_ch_pos);
+
+        while (lma_start_[fixed_lmas_] < fixed_hzs_)
+          fixed_lmas_++;
+        assert(lma_start_[fixed_lmas_] == fixed_hzs_);
+      }
+
+      // Re-search the Pinyin string for the unlocked lemma
+      // which was previously fixed.
+      //
+      // Prepare mile stones of this step to clear.
+      MileStoneHandle *dict_handles_to_clear = NULL;
+      if (clear_dmi_this_step && ch_pos == fixed_ch_pos &&
+          matrix_[fixed_ch_pos].dmi_num > 0) {
+        dict_handles_to_clear = dmi_pool_[matrix_[fixed_ch_pos].dmi_pos].dict_handles;
+      }
+
+      // If there are more steps, and this step is not allowed to clear, find
+      // milestones of next step.
+      if (pys_decoded_len_ > fixed_ch_pos && !clear_dmi_this_step) {
+        dict_handles_to_clear = NULL;
+        if (matrix_[fixed_ch_pos + 1].dmi_num > 0) {
+          dict_handles_to_clear =
+              dmi_pool_[matrix_[fixed_ch_pos + 1].dmi_pos].dict_handles;
+        }
+      }
+
+      if (NULL != dict_handles_to_clear) {
+        dict_trie_->reset_milestones(fixed_ch_pos, dict_handles_to_clear[0]);
+        if (NULL != user_dict_)
+          user_dict_->reset_milestones(fixed_ch_pos, dict_handles_to_clear[1]);
+      }
+
+
+      pys_decoded_len_ = fixed_ch_pos;
+
+      if (clear_dmi_this_step && ch_pos == fixed_ch_pos) {
+        dmi_pool_used_ = matrix_[fixed_ch_pos - 1].dmi_pos
+                         + matrix_[fixed_ch_pos - 1].dmi_num;
+        matrix_[fixed_ch_pos].dmi_num = 0;
+      } else {
+        dmi_pool_used_ = matrix_[fixed_ch_pos].dmi_pos +
+            matrix_[fixed_ch_pos].dmi_num;
+      }
+
+      if (clear_mtrx_this_step && ch_pos == fixed_ch_pos) {
+        mtrx_nd_pool_used_ = matrix_[fixed_ch_pos - 1].mtrx_nd_pos
+                             + matrix_[fixed_ch_pos - 1].mtrx_nd_num;
+        matrix_[fixed_ch_pos].mtrx_nd_num = 0;
+      } else {
+        mtrx_nd_pool_used_ = matrix_[fixed_ch_pos].mtrx_nd_pos
+                             + matrix_[fixed_ch_pos].mtrx_nd_num;
+      }
+
+      for (uint16 re_pos = fixed_ch_pos; re_pos < ch_pos; re_pos++) {
+        add_char(pys_[re_pos]);
+      }
+    } else if (kLemmaIdComposing == lma_id_[0]) {
+      // Do nothing for the composing phrase.
+    }
+  }
+
+  return true;
+}
+
+void MatrixSearch::del_in_pys(size_t start, size_t len) {
+  while (start < kMaxRowNum - len && '\0' != pys_[start]) {
+    pys_[start] = pys_[start + len];
+    start++;
+  }
+}
+
+size_t MatrixSearch::search(const char *py, size_t py_len) {
+  if (!inited_ || NULL == py)
+    return 0;
+
+  // If the search Pinyin string is too long, it will be truncated.
+  if (py_len > kMaxRowNum - 1)
+    py_len = kMaxRowNum - 1;
+
+  // Compare the new string with the previous one. Find their prefix to
+  // increase search efficiency.
+  size_t ch_pos = 0;
+  for (ch_pos = 0; ch_pos < pys_decoded_len_; ch_pos++) {
+    if ('\0' == py[ch_pos] || py[ch_pos] != pys_[ch_pos])
+      break;
+  }
+
+  bool clear_fix = true;
+  if (ch_pos == pys_decoded_len_)
+    clear_fix = false;
+
+  reset_search(ch_pos, clear_fix, false, false);
+
+  memcpy(pys_ + ch_pos, py + ch_pos, py_len - ch_pos);
+  pys_[py_len] = '\0';
+
+  while ('\0' != pys_[ch_pos]) {
+    if (!add_char(py[ch_pos])) {
+      pys_decoded_len_ = ch_pos;
+      break;
+    }
+    ch_pos++;
+  }
+
+  // Get spelling ids and starting positions.
+  get_spl_start_id();
+
+  // If there are too many spellings, remove the last letter until the spelling
+  // number is acceptable.
+  while (spl_id_num_ > 9) {
+    py_len--;
+    reset_search(py_len, false, false, false);
+    pys_[py_len] = '\0';
+    get_spl_start_id();
+  }
+
+  prepare_candidates();
+
+  if (kPrintDebug0) {
+    printf("--Matrix Node Pool Used: %d\n", mtrx_nd_pool_used_);
+    printf("--DMI Pool Used: %d\n", dmi_pool_used_);
+
+    if (kPrintDebug1) {
+      for (PoolPosType pos = 0; pos < dmi_pool_used_; pos++) {
+        debug_print_dmi(pos, 1);
+      }
+    }
+  }
+
+  return ch_pos;
+}
+
+size_t MatrixSearch::delsearch(size_t pos, bool is_pos_in_splid,
+                               bool clear_fixed_this_step) {
+  if (!inited_)
+    return 0;
+
+  // Out of range for both Pinyin mode and Spelling id mode.
+  if (pys_decoded_len_ <= pos) {
+    del_in_pys(pos, 1);
+    return pys_decoded_len_;
+  }
+
+  // Spelling id mode, but out of range.
+  if (is_pos_in_splid && spl_id_num_ <= pos)
+    return pys_decoded_len_;
+
+  // Begin to handle two modes respectively.
+  // Pinyin mode by default
+  size_t c_py_len = 0;  // The length of composing phrase's Pinyin
+  size_t reset_pos = pos;
+  size_t del_py_len = 1;
+  size_t stop_pos = pys_decoded_len_;
+  if (!is_pos_in_splid) {
+    // Pinyin mode is only allowed to delete beyond the fixed lemmas.
+    if (fixed_lmas_ > 0 && pos < spl_start_[lma_start_[fixed_lmas_]])
+      return pys_decoded_len_;
+
+    del_in_pys(pos, 1);
+
+    // If the deleted character is just the one after the last fixed lemma
+    if (pos == spl_start_[lma_start_[fixed_lmas_]]) {
+      // If all fixed lemmas have been merged, and the caller of the function
+      // request to unlock the last fixed lemma.
+      if (kLemmaIdComposing == lma_id_[0] && clear_fixed_this_step) {
+        // Unlock the last sub lemma in the composing phrase. Because it is not
+        // easy to unlock it directly. Instead, we re-decode the modified
+        // composing phrase.
+        c_phrase_.sublma_num--;
+        c_phrase_.length = c_phrase_.sublma_start[c_phrase_.sublma_num];
+        reset_pos = spl_start_[c_phrase_.length];
+        c_py_len = reset_pos;
+      }
+    }
+  } else {
+    del_py_len = spl_start_[pos + 1] - spl_start_[pos];
+
+    del_in_pys(spl_start_[pos], del_py_len);
+
+    if (pos >= lma_start_[fixed_lmas_]) {
+      c_py_len = 0;
+      reset_pos = spl_start_[pos + 1] - del_py_len;
+    } else {
+      c_py_len = spl_start_[lma_start_[fixed_lmas_]] - del_py_len;
+      reset_pos = c_py_len;
+      if (c_py_len > 0)
+        merge_fixed_lmas(pos);
+    }
+  }
+
+  if (c_py_len > 0) {
+    assert(c_phrase_.length > 0 && c_py_len ==
+        c_phrase_.spl_start[c_phrase_.sublma_start[c_phrase_.sublma_num]]);
+    // The composing phrase is valid, reset all search space,
+    // and begin a new search which will only extend the composing
+    // phrase.
+    reset_search(0, true, true, true);
+
+    dmi_c_phrase_ = true;
+    // Extend the composing phrase.
+    uint16 c_py_pos = 0;
+    while (c_py_pos < c_py_len) {
+      bool b_ac_tmp = add_char(pys_[c_py_pos]);
+      assert(b_ac_tmp);
+      c_py_pos++;
+    }
+    dmi_c_phrase_ = false;
+
+    // Fixd the composing phrase as the first choice.
+    lma_id_num_ = 1;
+    fixed_lmas_ = 1;
+    fixed_lmas_no1_[0] = 0;  // A composing string is always modified.
+    fixed_hzs_ = c_phrase_.length;
+    lma_start_[1] = fixed_hzs_;
+    lma_id_[0] = kLemmaIdComposing;
+    matrix_[spl_start_[fixed_hzs_]].mtrx_nd_fixed = mtrx_nd_pool_ +
+        matrix_[spl_start_[fixed_hzs_]].mtrx_nd_pos;
+  } else {
+    // Reseting search only clear pys_decoded_len_, but the string is kept.
+    reset_search(reset_pos, clear_fixed_this_step, false, false);
+  }
+
+  // Decode the string after the delete position.
+  stop_pos -= del_py_len;
+  while (reset_pos < stop_pos) {
+    if (!add_char(pys_[reset_pos])) {
+      pys_decoded_len_ = reset_pos;
+      break;
+    }
+    reset_pos++;
+  }
+
+  get_spl_start_id();
+  prepare_candidates();
+  return c_py_len;
+}
+
+size_t MatrixSearch::get_candidate_num() {
+  if (!inited_ || 0 == pys_decoded_len_ ||
+      0 == matrix_[pys_decoded_len_].mtrx_nd_num)
+    return 0;
+
+  return 1 + lpi_total_;
+}
+
+char16* MatrixSearch::get_candidate(size_t cand_id, char16 *cand_str,
+                                    size_t max_len) {
+  if (!inited_ || 0 == pys_decoded_len_ || NULL == cand_str)
+    return NULL;
+
+  if (0 == cand_id) {
+    return get_candidate0(cand_str, max_len, NULL, false);
+  } else {
+    cand_id--;
+  }
+
+  // For this case: the current sentence is a word only, and the user fixed it,
+  // so the result will be fixed to the sentence space, and
+  // lpi_total_ will be set to 0.
+  if (0 == lpi_total_) {
+    return get_candidate0(cand_str, max_len, NULL, false);
+  }
+
+  LemmaIdType id = lpi_items_[cand_id].id;
+  char16 s[kMaxLemmaSize + 1];
+
+  uint16 s_len = lpi_items_[cand_id].lma_len;
+  if (s_len > 1) {
+    s_len = get_lemma_str(id, s, kMaxLemmaSize + 1);
+  } else {
+    // For a single character, Hanzi is ready.
+    s[0] = lpi_items_[cand_id].hanzi;
+    s[1] = static_cast<char16>(0);
+  }
+
+  if (s_len > 0 &&  max_len > s_len) {
+    utf16_strncpy(cand_str, s, s_len);
+    cand_str[s_len] = (char16)'\0';
+    return cand_str;
+  }
+
+  return NULL;
+}
+
+void MatrixSearch::update_dict_freq() {
+  if (NULL != user_dict_) {
+    // Update the total frequency of all lemmas, including system lemmas and
+    // user dictionary lemmas.
+    size_t total_freq = user_dict_->get_total_lemma_count();
+    dict_trie_->set_total_lemma_count_of_others(total_freq);
+  }
+}
+
+bool MatrixSearch::add_lma_to_userdict(uint16 lma_fr, uint16 lma_to,
+                                       float score) {
+  if (lma_to - lma_fr <= 1 || NULL == user_dict_)
+    return false;
+
+  char16 word_str[kMaxLemmaSize + 1];
+  uint16 spl_ids[kMaxLemmaSize];
+
+  uint16 spl_id_fr = 0;
+
+  for (uint16 pos = lma_fr; pos < lma_to; pos++) {
+    LemmaIdType lma_id = lma_id_[pos];
+    if (is_user_lemma(lma_id)) {
+      user_dict_->update_lemma(lma_id, 1, true);
+    }
+    uint16 lma_len = lma_start_[pos + 1] - lma_start_[pos];
+    utf16_strncpy(spl_ids + spl_id_fr, spl_id_ + lma_start_[pos], lma_len);
+
+    uint16 tmp = get_lemma_str(lma_id, word_str + spl_id_fr,
+                               kMaxLemmaSize + 1 - spl_id_fr);
+    assert(tmp == lma_len);
+
+    tmp = get_lemma_splids(lma_id, spl_ids + spl_id_fr, lma_len, true);
+    if (tmp != lma_len) {
+      return false;
+    }
+
+    spl_id_fr += lma_len;
+  }
+
+  assert(spl_id_fr <= kMaxLemmaSize);
+
+  return user_dict_->put_lemma(static_cast<char16*>(word_str), spl_ids,
+                                 spl_id_fr, 1);
+}
+
+void MatrixSearch::debug_print_dmi(PoolPosType dmi_pos, uint16 nest_level) {
+  if (dmi_pos >= dmi_pool_used_) return;
+
+  DictMatchInfo *dmi = dmi_pool_ + dmi_pos;
+
+  if (1 == nest_level) {
+    printf("-----------------%d\'th DMI node begin----------->\n", dmi_pos);
+  }
+  if (dmi->dict_level > 1) {
+    debug_print_dmi(dmi->dmi_fr, nest_level + 1);
+  }
+  printf("---%d\n", dmi->dict_level);
+  printf(" MileStone: %x, %x\n", dmi->dict_handles[0], dmi->dict_handles[1]);
+  printf(" Spelling : %s, %d\n", SpellingTrie::get_instance().
+         get_spelling_str(dmi->spl_id), dmi->spl_id);
+  printf(" Total Pinyin Len: %d\n", dmi->splstr_len);
+  if (1 == nest_level) {
+    printf("<----------------%d\'th DMI node end--------------\n\n", dmi_pos);
+  }
+}
+
+bool MatrixSearch::try_add_cand0_to_userdict() {
+  size_t new_cand_num = get_candidate_num();
+  if (fixed_hzs_ > 0 && 1 == new_cand_num) {
+    float score_from = 0;
+    uint16 lma_id_from = 0;
+    uint16 pos = 0;
+    bool modified = false;
+    while (pos < fixed_lmas_) {
+      if (lma_start_[pos + 1] - lma_start_[lma_id_from] >
+          static_cast<uint16>(kMaxLemmaSize)) {
+        float score_to_add =
+            mtrx_nd_pool_[matrix_[spl_start_[lma_start_[pos]]]
+            .mtrx_nd_pos].score - score_from;
+        if (modified) {
+          score_to_add += 1.0;
+          if (score_to_add > NGram::kMaxScore) {
+            score_to_add = NGram::kMaxScore;
+          }
+          add_lma_to_userdict(lma_id_from, pos, score_to_add);
+        }
+        lma_id_from = pos;
+        score_from += score_to_add;
+
+        // Clear the flag for next user lemma.
+        modified = false;
+      }
+
+      if (0 == fixed_lmas_no1_[pos]) {
+        modified = true;
+      }
+      pos++;
+    }
+
+    // Single-char word is not allowed to add to userdict.
+    if (lma_start_[pos] - lma_start_[lma_id_from] > 1) {
+      float score_to_add =
+          mtrx_nd_pool_[matrix_[spl_start_[lma_start_[pos]]]
+          .mtrx_nd_pos].score - score_from;
+      if (modified) {
+        score_to_add += 1.0;
+        if (score_to_add > NGram::kMaxScore) {
+          score_to_add = NGram::kMaxScore;
+        }
+        add_lma_to_userdict(lma_id_from, pos, score_to_add);
+      }
+    }
+  }
+  return true;
+}
+
+// Choose a candidate, and give new candidates for next step.
+// If user finishes selection, we will try to communicate with user dictionary
+// to add new items or update score of some existing items.
+//
+// Basic rule:
+// 1. If user selects the first choice:
+//    1.1. If the first choice is not a sentence, instead, it is a lemma:
+//         1.1.1. If the first choice is a user lemma, notify the user
+//                dictionary that a user lemma is hit, and add occuring count
+//                by 1.
+//         1.1.2. If the first choice is a system lemma, do nothing.
+//    1.2. If the first choice is a sentence containing more than one lemma:
+//         1.2.1. The whole sentence will be added as a user lemma. If the
+//                sentence contains user lemmas, -> hit, and add occuring count
+//                by 1.
+size_t MatrixSearch::choose(size_t cand_id) {
+  if (!inited_ || 0 == pys_decoded_len_)
+    return 0;
+
+  if (0 == cand_id) {
+    fixed_hzs_ = spl_id_num_;
+    matrix_[spl_start_[fixed_hzs_]].mtrx_nd_fixed = mtrx_nd_pool_ +
+        matrix_[spl_start_[fixed_hzs_]].mtrx_nd_pos;
+    for (size_t pos = fixed_lmas_; pos < lma_id_num_; pos++) {
+      fixed_lmas_no1_[pos] = 1;
+    }
+    fixed_lmas_ = lma_id_num_;
+    lpi_total_ = 0;  // Clean all other candidates.
+
+    // 1. It is the first choice
+    if (1 == lma_id_num_) {
+      // 1.1. The first choice is not a sentence but a lemma
+      if (is_user_lemma(lma_id_[0])) {
+        // 1.1.1. The first choice is a user lemma, notify the user dictionary
+        // that it is hit.
+        if (NULL != user_dict_)
+          user_dict_->update_lemma(lma_id_[0], 1, true);
+      } else {
+        // 1.1.2. do thing for a system lemma.
+      }
+    } else {
+      // 1.2. The first choice is a sentence.
+      // 1.2.1 Try to add the whole sentence to user dictionary, the whole
+      // sentence may be splitted into many items.
+      if (NULL != user_dict_) {
+        try_add_cand0_to_userdict();
+      }
+    }
+    update_dict_freq();
+    return 1;
+  } else {
+    cand_id--;
+  }
+
+  // 2. It is not the full sentence candidate.
+  // Find the length of the candidate.
+  LemmaIdType id_chosen = lpi_items_[cand_id].id;
+  LmaScoreType score_chosen = lpi_items_[cand_id].psb;
+  size_t cand_len = lpi_items_[cand_id].lma_len;
+
+  assert(cand_len > 0);
+
+  // Notify the atom dictionary that this item is hit.
+  if (is_user_lemma(id_chosen)) {
+    if (NULL != user_dict_) {
+      user_dict_->update_lemma(id_chosen, 1, true);
+    }
+    update_dict_freq();
+  }
+
+  // 3. Fixed the chosen item.
+  // 3.1 Get the steps number.
+  size_t step_fr = spl_start_[fixed_hzs_];
+  size_t step_to = spl_start_[fixed_hzs_ + cand_len];
+
+  // 3.2 Save the length of the original string.
+  size_t pys_decoded_len = pys_decoded_len_;
+
+  // 3.2 Reset the space of the fixed part.
+  reset_search(step_to, false, false, true);
+
+  // 3.3 For the last character of the fixed part, the previous DMI
+  // information will be kept, while the MTRX information will be re-extended,
+  // and only one node will be extended.
+  matrix_[step_to].mtrx_nd_num = 0;
+
+  LmaPsbItem lpi_item;
+  lpi_item.psb = score_chosen;
+  lpi_item.id = id_chosen;
+
+  PoolPosType step_to_dmi_fr = match_dmi(step_to,
+                                         spl_id_ + fixed_hzs_, cand_len);
+  assert(step_to_dmi_fr != static_cast<PoolPosType>(-1));
+
+  extend_mtrx_nd(matrix_[step_fr].mtrx_nd_fixed, &lpi_item, 1,
+                 step_to_dmi_fr, step_to);
+
+  matrix_[step_to].mtrx_nd_fixed = mtrx_nd_pool_ + matrix_[step_to].mtrx_nd_pos;
+  mtrx_nd_pool_used_ = matrix_[step_to].mtrx_nd_pos +
+                       matrix_[step_to].mtrx_nd_num;
+
+  if (id_chosen == lma_id_[fixed_lmas_])
+    fixed_lmas_no1_[fixed_lmas_] = 1;
+  else
+    fixed_lmas_no1_[fixed_lmas_] = 0;
+  lma_id_[fixed_lmas_] = id_chosen;
+  lma_start_[fixed_lmas_ + 1] = lma_start_[fixed_lmas_] + cand_len;
+  fixed_lmas_++;
+  fixed_hzs_ = fixed_hzs_ + cand_len;
+
+  while (step_to != pys_decoded_len) {
+    bool b = add_char(pys_[step_to]);
+    assert(b);
+    step_to++;
+  }
+
+  if (fixed_hzs_ < spl_id_num_) {
+    prepare_candidates();
+  } else {
+    lpi_total_ = 0;
+    if (NULL != user_dict_) {
+      try_add_cand0_to_userdict();
+    }
+  }
+
+  return get_candidate_num();
+}
+
+size_t MatrixSearch::cancel_last_choice() {
+  if (!inited_ || 0 == pys_decoded_len_)
+    return 0;
+
+  size_t step_start = 0;
+  if (fixed_hzs_ > 0) {
+    size_t step_end = spl_start_[fixed_hzs_];
+    MatrixNode *end_node = matrix_[step_end].mtrx_nd_fixed;
+    assert(NULL != end_node);
+
+    step_start = end_node->from->step;
+
+    if (step_start > 0) {
+      DictMatchInfo *dmi = dmi_pool_ + end_node->dmi_fr;
+      fixed_hzs_ -= dmi->dict_level;
+    } else {
+      fixed_hzs_ = 0;
+    }
+
+    reset_search(step_start, false, false, false);
+
+    while (pys_[step_start] != '\0') {
+      bool b = add_char(pys_[step_start]);
+      assert(b);
+      step_start++;
+    }
+
+    prepare_candidates();
+  }
+  return get_candidate_num();
+}
+
+size_t MatrixSearch::get_fixedlen() {
+  if (!inited_ || 0 == pys_decoded_len_)
+    return 0;
+  return fixed_hzs_;
+}
+
+bool MatrixSearch::prepare_add_char(char ch) {
+  if (pys_decoded_len_ >= kMaxRowNum - 1 ||
+      (!spl_parser_->is_valid_to_parse(ch) && ch != '\''))
+    return false;
+
+  if (dmi_pool_used_ >= kDmiPoolSize) return false;
+
+  pys_[pys_decoded_len_] = ch;
+  pys_decoded_len_++;
+
+  MatrixRow *mtrx_this_row = matrix_ + pys_decoded_len_;
+  mtrx_this_row->mtrx_nd_pos = mtrx_nd_pool_used_;
+  mtrx_this_row->mtrx_nd_num = 0;
+  mtrx_this_row->dmi_pos = dmi_pool_used_;
+  mtrx_this_row->dmi_num = 0;
+  mtrx_this_row->dmi_has_full_id = 0;
+
+  return true;
+}
+
+bool MatrixSearch::is_split_at(uint16 pos) {
+  return !spl_parser_->is_valid_to_parse(pys_[pos - 1]);
+}
+
+void MatrixSearch::fill_dmi(DictMatchInfo *dmi, MileStoneHandle *handles,
+                            PoolPosType dmi_fr, uint16 spl_id,
+                            uint16 node_num, unsigned char dict_level,
+                            bool splid_end_split, unsigned char splstr_len,
+                            unsigned char all_full_id) {
+  dmi->dict_handles[0] = handles[0];
+  dmi->dict_handles[1] = handles[1];
+  dmi->dmi_fr = dmi_fr;
+  dmi->spl_id = spl_id;
+  dmi->dict_level = dict_level;
+  dmi->splid_end_split = splid_end_split ? 1 : 0;
+  dmi->splstr_len = splstr_len;
+  dmi->all_full_id = all_full_id;
+  dmi->c_phrase = 0;
+}
+
+bool MatrixSearch::add_char(char ch) {
+  if (!prepare_add_char(ch))
+    return false;
+  return add_char_qwerty();
+}
+
+bool MatrixSearch::add_char_qwerty() {
+  matrix_[pys_decoded_len_].mtrx_nd_num = 0;
+
+  bool spl_matched = false;
+  uint16 longest_ext = 0;
+  // Extend the search matrix, from the oldest unfixed row. ext_len means
+  // extending length.
+  for (uint16 ext_len = kMaxPinyinSize + 1; ext_len > 0; ext_len--) {
+    if (ext_len > pys_decoded_len_ - spl_start_[fixed_hzs_])
+      continue;
+
+    // Refer to the declaration of the variable dmi_has_full_id for the
+    // explanation of this piece of code. In one word, it is used to prevent
+    // from the unwise extending of "shoud ou" but allow the reasonable
+    // extending of "heng ao", "lang a", etc.
+    if (ext_len > 1 && 0 != longest_ext &&
+        0 == matrix_[pys_decoded_len_ - ext_len].dmi_has_full_id) {
+      if (xi_an_enabled_)
+        continue;
+      else
+        break;
+    }
+
+    uint16 oldrow = pys_decoded_len_ - ext_len;
+
+    // 0. If that row is before the last fixed step, ignore.
+    if (spl_start_[fixed_hzs_] > oldrow)
+      continue;
+
+    // 1. Check if that old row has valid MatrixNode. If no, means that row is
+    // not a boundary, either a word boundary or a spelling boundary.
+    // If it is for extending composing phrase, it's OK to ignore the 0.
+    if (0 == matrix_[oldrow].mtrx_nd_num && !dmi_c_phrase_)
+      continue;
+
+    // 2. Get spelling id(s) for the last ext_len chars.
+    uint16 spl_idx;
+    bool is_pre = false;
+    spl_idx = spl_parser_->get_splid_by_str(pys_ + oldrow,
+                                            ext_len, &is_pre);
+    if (is_pre)
+      spl_matched = true;
+
+    if (0 == spl_idx)
+      continue;
+
+    bool splid_end_split = is_split_at(oldrow + ext_len);
+
+    // 3. Extend the DMI nodes of that old row
+    // + 1 is to extend an extra node from the root
+    for (PoolPosType dmi_pos = matrix_[oldrow].dmi_pos;
+         dmi_pos < matrix_[oldrow].dmi_pos + matrix_[oldrow].dmi_num + 1;
+         dmi_pos++) {
+      DictMatchInfo *dmi = dmi_pool_ + dmi_pos;
+      if (dmi_pos == matrix_[oldrow].dmi_pos + matrix_[oldrow].dmi_num) {
+        dmi = NULL;  // The last one, NULL means extending from the root.
+      } else {
+        // If the dmi is covered by the fixed arrange, ignore it.
+        if (fixed_hzs_ > 0 &&
+            pys_decoded_len_ - ext_len - dmi->splstr_len <
+            spl_start_[fixed_hzs_]) {
+          continue;
+        }
+        // If it is not in mode for composing phrase, and the source DMI node
+        // is marked for composing phrase, ignore this node.
+        if (dmi->c_phrase != 0 && !dmi_c_phrase_) {
+          continue;
+        }
+      }
+
+      // For example, if "gao" is extended, "g ao" is not allowed.
+      // or "zh" has been passed, "z h" is not allowed.
+      // Both word and word-connection will be prevented.
+      if (longest_ext > ext_len) {
+        if (NULL == dmi && 0 == matrix_[oldrow].dmi_has_full_id) {
+          continue;
+        }
+
+        // "z h" is not allowed.
+        if (NULL != dmi && spl_trie_->is_half_id(dmi->spl_id)) {
+          continue;
+        }
+      }
+
+      dep_->splids_extended = 0;
+      if (NULL != dmi) {
+        uint16 prev_ids_num = dmi->dict_level;
+        if ((!dmi_c_phrase_ && prev_ids_num >= kMaxLemmaSize) ||
+            (dmi_c_phrase_ && prev_ids_num >=  kMaxRowNum)) {
+          continue;
+        }
+
+        DictMatchInfo *d = dmi;
+        while (d) {
+          dep_->splids[--prev_ids_num] = d->spl_id;
+          if ((PoolPosType)-1 == d->dmi_fr)
+            break;
+          d = dmi_pool_ + d->dmi_fr;
+        }
+        assert(0 == prev_ids_num);
+        dep_->splids_extended = dmi->dict_level;
+      }
+      dep_->splids[dep_->splids_extended] = spl_idx;
+      dep_->ext_len = ext_len;
+      dep_->splid_end_split = splid_end_split;
+
+      dep_->id_num = 1;
+      dep_->id_start = spl_idx;
+      if (spl_trie_->is_half_id(spl_idx)) {
+        // Get the full id list
+        dep_->id_num = spl_trie_->half_to_full(spl_idx, &(dep_->id_start));
+        assert(dep_->id_num > 0);
+      }
+
+      uint16 new_dmi_num;
+
+      new_dmi_num = extend_dmi(dep_, dmi);
+
+      if (new_dmi_num > 0) {
+        if (dmi_c_phrase_) {
+          dmi_pool_[dmi_pool_used_].c_phrase = 1;
+        }
+        matrix_[pys_decoded_len_].dmi_num += new_dmi_num;
+        dmi_pool_used_ += new_dmi_num;
+
+        if (!spl_trie_->is_half_id(spl_idx))
+          matrix_[pys_decoded_len_].dmi_has_full_id = 1;
+      }
+
+      // If get candiate lemmas, try to extend the path
+      if (lpi_total_ > 0) {
+        uint16 fr_row;
+        if (NULL == dmi) {
+          fr_row = oldrow;
+        } else {
+          assert(oldrow >= dmi->splstr_len);
+          fr_row = oldrow - dmi->splstr_len;
+        }
+        for (PoolPosType mtrx_nd_pos = matrix_[fr_row].mtrx_nd_pos;
+             mtrx_nd_pos < matrix_[fr_row].mtrx_nd_pos +
+             matrix_[fr_row].mtrx_nd_num;
+             mtrx_nd_pos++) {
+          MatrixNode *mtrx_nd = mtrx_nd_pool_ + mtrx_nd_pos;
+
+          extend_mtrx_nd(mtrx_nd, lpi_items_, lpi_total_,
+                         dmi_pool_used_ - new_dmi_num, pys_decoded_len_);
+          if (longest_ext == 0)
+            longest_ext = ext_len;
+        }
+      }
+    }  // for dmi_pos
+  }  // for ext_len
+  mtrx_nd_pool_used_ += matrix_[pys_decoded_len_].mtrx_nd_num;
+
+  if (dmi_c_phrase_)
+    return true;
+
+  return (matrix_[pys_decoded_len_].mtrx_nd_num != 0 || spl_matched);
+}
+
+void MatrixSearch::prepare_candidates() {
+  // Get candiates from the first un-fixed step.
+  uint16 lma_size_max = kMaxLemmaSize;
+  if (lma_size_max > spl_id_num_ - fixed_hzs_)
+    lma_size_max = spl_id_num_ - fixed_hzs_;
+
+  uint16 lma_size = lma_size_max;
+
+  // If the full sentense candidate's unfixed part may be the same with a normal
+  // lemma. Remove the lemma candidate in this case.
+  char16 fullsent[kMaxLemmaSize + 1];
+  char16 *pfullsent = NULL;
+  uint16 sent_len;
+  pfullsent = get_candidate0(fullsent, kMaxLemmaSize + 1, &sent_len, true);
+
+  // If the unfixed part contains more than one ids, it is not necessary to
+  // check whether a lemma's string is the same to the unfixed part of the full
+  // sentence candidate, so, set it to NULL;
+  if (sent_len > kMaxLemmaSize)
+    pfullsent = NULL;
+
+  lpi_total_ = 0;
+  size_t lpi_num_full_match = 0;  // Number of items which are fully-matched.
+  while (lma_size > 0) {
+    size_t lma_num;
+    lma_num = get_lpis(spl_id_ + fixed_hzs_, lma_size,
+                       lpi_items_ + lpi_total_,
+                       size_t(kMaxLmaPsbItems - lpi_total_),
+                       pfullsent, lma_size == lma_size_max);
+
+    if (lma_num > 0) {
+      lpi_total_ += lma_num;
+      // For next lemma candidates which are not the longest, it is not
+      // necessary to compare with the full sentence candiate.
+      pfullsent = NULL;
+    }
+    if (lma_size == lma_size_max) {
+      lpi_num_full_match = lpi_total_;
+    }
+    lma_size--;
+  }
+
+  // Sort those partially-matched items by their unified scores.
+  myqsort(lpi_items_ + lpi_num_full_match, lpi_total_ - lpi_num_full_match,
+          sizeof(LmaPsbItem), cmp_lpi_with_unified_psb);
+
+  if (kPrintDebug0) {
+    printf("-----Prepare candidates, score:\n");
+    for (size_t a = 0; a < lpi_total_; a++) {
+      printf("[%03d]%d    ", a, lpi_items_[a].psb);
+      if ((a + 1) % 6 == 0) printf("\n");
+    }
+    printf("\n");
+  }
+
+  if (kPrintDebug0) {
+    printf("--- lpi_total_ = %d\n", lpi_total_);
+  }
+}
+
+const char* MatrixSearch::get_pystr(size_t *decoded_len) {
+  if (!inited_ || NULL == decoded_len)
+    return NULL;
+
+  *decoded_len = pys_decoded_len_;
+  return pys_;
+}
+
+void MatrixSearch::merge_fixed_lmas(size_t del_spl_pos) {
+  if (fixed_lmas_ == 0)
+    return;
+  // Update spelling segmentation information first.
+  spl_id_num_ -= 1;
+  uint16 del_py_len = spl_start_[del_spl_pos + 1] - spl_start_[del_spl_pos];
+  for (size_t pos = del_spl_pos; pos <= spl_id_num_; pos++) {
+    spl_start_[pos] = spl_start_[pos + 1] - del_py_len;
+    if (pos == spl_id_num_)
+      break;
+    spl_id_[pos] = spl_id_[pos + 1];
+  }
+
+  // Begin to merge.
+  uint16 phrase_len = 0;
+
+  // Update the spelling ids to the composing phrase.
+  // We need to convert these ids into full id in the future.
+  memcpy(c_phrase_.spl_ids, spl_id_, spl_id_num_ * sizeof(uint16));
+  memcpy(c_phrase_.spl_start, spl_start_, (spl_id_num_ + 1) * sizeof(uint16));
+
+  // If composing phrase has not been created, first merge all fixed
+  //  lemmas into a composing phrase without deletion.
+  if (fixed_lmas_ > 1 || kLemmaIdComposing != lma_id_[0]) {
+    uint16 bp = 1;  // Begin position of real fixed lemmas.
+    // There is no existing composing phrase.
+    if (kLemmaIdComposing != lma_id_[0]) {
+      c_phrase_.sublma_num = 0;
+      bp = 0;
+    }
+
+    uint16 sub_num = c_phrase_.sublma_num;
+    for (uint16 pos = bp; pos <= fixed_lmas_; pos++) {
+      c_phrase_.sublma_start[sub_num + pos - bp] = lma_start_[pos];
+      if (lma_start_[pos] > del_spl_pos) {
+        c_phrase_.sublma_start[sub_num + pos - bp] -= 1;
+      }
+
+      if (pos == fixed_lmas_)
+        break;
+
+      uint16 lma_len;
+      char16 *lma_str = c_phrase_.chn_str +
+          c_phrase_.sublma_start[sub_num] + phrase_len;
+
+      lma_len = get_lemma_str(lma_id_[pos], lma_str, kMaxRowNum - phrase_len);
+      assert(lma_len == lma_start_[pos + 1] - lma_start_[pos]);
+      phrase_len += lma_len;
+    }
+    assert(phrase_len == lma_start_[fixed_lmas_]);
+    c_phrase_.length = phrase_len;  // will be deleted by 1
+    c_phrase_.sublma_num += fixed_lmas_ - bp;
+  } else {
+    for (uint16 pos = 0; pos <= c_phrase_.sublma_num; pos++) {
+      if (c_phrase_.sublma_start[pos] > del_spl_pos) {
+        c_phrase_.sublma_start[pos] -= 1;
+      }
+    }
+    phrase_len = c_phrase_.length;
+  }
+
+  assert(phrase_len > 0);
+  if (1 == phrase_len) {
+    // After the only one is deleted, nothing will be left.
+    fixed_lmas_ = 0;
+    return;
+  }
+
+  // Delete the Chinese character in the merged phrase.
+  // The corresponding elements in spl_ids and spl_start of the
+  // phrase have been deleted.
+  char16 *chn_str = c_phrase_.chn_str + del_spl_pos;
+  for (uint16 pos = 0;
+      pos < c_phrase_.sublma_start[c_phrase_.sublma_num] - del_spl_pos;
+      pos++) {
+    chn_str[pos] = chn_str[pos + 1];
+  }
+  c_phrase_.length -= 1;
+
+  // If the deleted spelling id is in a sub lemma which contains more than
+  // one id, del_a_sub will be false; but if the deleted id is in a sub lemma
+  // which only contains 1 id, the whole sub lemma needs to be deleted, so
+  // del_a_sub will be true.
+  bool del_a_sub = false;
+  for (uint16 pos = 1; pos <= c_phrase_.sublma_num; pos++) {
+    if (c_phrase_.sublma_start[pos - 1] ==
+        c_phrase_.sublma_start[pos]) {
+      del_a_sub = true;
+    }
+    if (del_a_sub) {
+      c_phrase_.sublma_start[pos - 1] =
+          c_phrase_.sublma_start[pos];
+    }
+  }
+  if (del_a_sub)
+    c_phrase_.sublma_num -= 1;
+
+  return;
+}
+
+void MatrixSearch::get_spl_start_id() {
+  lma_id_num_ = 0;
+  lma_start_[0] = 0;
+
+  spl_id_num_ = 0;
+  spl_start_[0] = 0;
+  if (!inited_ || 0 == pys_decoded_len_ ||
+      0 == matrix_[pys_decoded_len_].mtrx_nd_num)
+    return;
+
+  // Calculate number of lemmas and spellings
+  // Only scan those part which is not fixed.
+  lma_id_num_ = fixed_lmas_;
+  spl_id_num_ = fixed_hzs_;
+
+  MatrixNode *mtrx_nd = mtrx_nd_pool_ + matrix_[pys_decoded_len_].mtrx_nd_pos;
+  while (mtrx_nd != mtrx_nd_pool_) {
+    if (fixed_hzs_ > 0) {
+      if (mtrx_nd->step <= spl_start_[fixed_hzs_])
+        break;
+    }
+
+    // Update the spelling segamentation information
+    unsigned char word_splstr_len = 0;
+    PoolPosType dmi_fr = mtrx_nd->dmi_fr;
+    if ((PoolPosType)-1 != dmi_fr)
+      word_splstr_len = dmi_pool_[dmi_fr].splstr_len;
+
+    while ((PoolPosType)-1 != dmi_fr) {
+      spl_start_[spl_id_num_ + 1] = mtrx_nd->step -
+          (word_splstr_len - dmi_pool_[dmi_fr].splstr_len);
+      spl_id_[spl_id_num_] = dmi_pool_[dmi_fr].spl_id;
+      spl_id_num_++;
+      dmi_fr = dmi_pool_[dmi_fr].dmi_fr;
+    }
+
+    // Update the lemma segmentation information
+    lma_start_[lma_id_num_ + 1] = spl_id_num_;
+    lma_id_[lma_id_num_] = mtrx_nd->id;
+    lma_id_num_++;
+
+    mtrx_nd = mtrx_nd->from;
+  }
+
+  // Reverse the result of spelling info
+  for (size_t pos = fixed_hzs_;
+       pos < fixed_hzs_ + (spl_id_num_ - fixed_hzs_ + 1) / 2; pos++) {
+    if (spl_id_num_ + fixed_hzs_ - pos != pos + 1) {
+      spl_start_[pos + 1] ^= spl_start_[spl_id_num_ - pos + fixed_hzs_];
+      spl_start_[spl_id_num_ - pos + fixed_hzs_] ^= spl_start_[pos + 1];
+      spl_start_[pos + 1] ^= spl_start_[spl_id_num_ - pos + fixed_hzs_];
+
+      spl_id_[pos] ^= spl_id_[spl_id_num_ + fixed_hzs_ - pos - 1];
+      spl_id_[spl_id_num_ + fixed_hzs_- pos - 1] ^= spl_id_[pos];
+      spl_id_[pos] ^= spl_id_[spl_id_num_ + fixed_hzs_- pos - 1];
+    }
+  }
+
+  // Reverse the result of lemma info
+  for (size_t pos = fixed_lmas_;
+       pos < fixed_lmas_ + (lma_id_num_ - fixed_lmas_ + 1) / 2; pos++) {
+    assert(lma_id_num_ + fixed_lmas_ - pos - 1 >= pos);
+
+    if (lma_id_num_ + fixed_lmas_ - pos > pos + 1) {
+      lma_start_[pos + 1] ^= lma_start_[lma_id_num_ - pos + fixed_lmas_];
+      lma_start_[lma_id_num_ - pos + fixed_lmas_] ^= lma_start_[pos + 1];
+      lma_start_[pos + 1] ^= lma_start_[lma_id_num_ - pos + fixed_lmas_];
+
+      lma_id_[pos] ^= lma_id_[lma_id_num_ - 1 - pos + fixed_lmas_];
+      lma_id_[lma_id_num_ - 1 - pos + fixed_lmas_] ^= lma_id_[pos];
+      lma_id_[pos] ^= lma_id_[lma_id_num_ - 1 - pos + fixed_lmas_];
+    }
+  }
+
+  for (size_t pos = fixed_lmas_ + 1; pos <= lma_id_num_; pos++) {
+    if (pos < lma_id_num_)
+      lma_start_[pos] = lma_start_[pos - 1] +
+          (lma_start_[pos] - lma_start_[pos + 1]);
+    else
+      lma_start_[pos] = lma_start_[pos - 1] + lma_start_[pos] -
+          lma_start_[fixed_lmas_];
+  }
+
+  // Find the last fixed position
+  fixed_hzs_ = 0;
+  for (size_t pos = spl_id_num_; pos > 0; pos--) {
+    if (NULL != matrix_[spl_start_[pos]].mtrx_nd_fixed) {
+      fixed_hzs_ = pos;
+      break;
+    }
+  }
+
+  return;
+}
+
+size_t MatrixSearch::get_spl_start(const uint16 *&spl_start) {
+  get_spl_start_id();
+  spl_start = spl_start_;
+  return spl_id_num_;
+}
+
+size_t MatrixSearch::extend_dmi(DictExtPara *dep, DictMatchInfo *dmi_s) {
+  if (dmi_pool_used_ >= kDmiPoolSize) return 0;
+
+  if (dmi_c_phrase_)
+    return extend_dmi_c(dep, dmi_s);
+
+  LpiCache& lpi_cache = LpiCache::get_instance();
+  uint16 splid = dep->splids[dep->splids_extended];
+
+  bool cached = false;
+  if (0 == dep->splids_extended)
+    cached = lpi_cache.is_cached(splid);
+
+  // 1. If this is a half Id, get its corresponding full starting Id and
+  // number of full Id.
+  size_t ret_val = 0;
+  PoolPosType mtrx_dmi_fr = (PoolPosType)-1;  // From which dmi node
+
+  lpi_total_ = 0;
+
+  MileStoneHandle from_h[3];
+  from_h[0] = 0;
+  from_h[1] = 0;
+
+  if (0 != dep->splids_extended) {
+    from_h[0] = dmi_s->dict_handles[0];
+    from_h[1] = dmi_s->dict_handles[1];
+  }
+
+  // 2. Begin exgtending in the system dictionary
+  size_t lpi_num = 0;
+  MileStoneHandle handles[2];
+  handles[0] = handles[1] = 0;
+  if (from_h[0] > 0 || NULL == dmi_s) {
+    handles[0] = dict_trie_->extend_dict(from_h[0], dep, lpi_items_,
+                                         kMaxLmaPsbItems, &lpi_num);
+  }
+  if (handles[0] > 0)
+    lpi_total_ = lpi_num;
+
+  if (NULL == dmi_s) {  // from root
+    assert(0 != handles[0]);
+    mtrx_dmi_fr = dmi_pool_used_;
+  }
+
+  // 3. Begin extending in the user dictionary
+  if (NULL != user_dict_ && (from_h[1] > 0 || NULL == dmi_s)) {
+    handles[1] = user_dict_->extend_dict(from_h[1], dep,
+                                         lpi_items_ + lpi_total_,
+                                         kMaxLmaPsbItems - lpi_total_,
+                                         &lpi_num);
+    if (handles[1] > 0) {
+      if (kPrintDebug0) {
+        for (size_t t = 0; t < lpi_num; t++) {
+          printf("--Extend in user dict: uid:%d uscore:%d\n", lpi_items_[lpi_total_ + t].id,
+                 lpi_items_[lpi_total_ + t].psb);
+        }
+      }
+      lpi_total_ += lpi_num;
+    }
+  }
+
+  if (0 != handles[0] || 0 != handles[1]) {
+    if (dmi_pool_used_ >= kDmiPoolSize) return 0;
+
+    DictMatchInfo *dmi_add = dmi_pool_ + dmi_pool_used_;
+    if (NULL == dmi_s) {
+      fill_dmi(dmi_add, handles,
+               (PoolPosType)-1, splid,
+               1, 1, dep->splid_end_split, dep->ext_len,
+               spl_trie_->is_half_id(splid) ? 0 : 1);
+    } else {
+      fill_dmi(dmi_add, handles,
+               dmi_s - dmi_pool_, splid, 1,
+               dmi_s->dict_level + 1, dep->splid_end_split,
+               dmi_s->splstr_len + dep->ext_len,
+               spl_trie_->is_half_id(splid) ? 0 : dmi_s->all_full_id);
+    }
+
+    ret_val = 1;
+  }
+
+  if (!cached) {
+    if (0 == lpi_total_)
+      return ret_val;
+
+    if (kPrintDebug0) {
+      printf("--- lpi_total_ = %d\n", lpi_total_);
+    }
+
+    myqsort(lpi_items_, lpi_total_, sizeof(LmaPsbItem), cmp_lpi_with_psb);
+    if (NULL == dmi_s && spl_trie_->is_half_id(splid))
+      lpi_total_ = lpi_cache.put_cache(splid, lpi_items_, lpi_total_);
+  } else {
+    assert(spl_trie_->is_half_id(splid));
+    lpi_total_ = lpi_cache.get_cache(splid, lpi_items_, kMaxLmaPsbItems);
+  }
+
+  return ret_val;
+}
+
+size_t MatrixSearch::extend_dmi_c(DictExtPara *dep, DictMatchInfo *dmi_s) {
+  lpi_total_ = 0;
+
+  uint16 pos = dep->splids_extended;
+  assert(dmi_c_phrase_);
+  if (pos >= c_phrase_.length)
+    return 0;
+
+  uint16 splid = dep->splids[pos];
+  if (splid == c_phrase_.spl_ids[pos]) {
+    DictMatchInfo *dmi_add = dmi_pool_ + dmi_pool_used_;
+    MileStoneHandle handles[2];  // Actually never used.
+    if (NULL == dmi_s)
+      fill_dmi(dmi_add, handles,
+               (PoolPosType)-1, splid,
+               1, 1, dep->splid_end_split, dep->ext_len,
+               spl_trie_->is_half_id(splid) ? 0 : 1);
+    else
+      fill_dmi(dmi_add, handles,
+               dmi_s - dmi_pool_, splid, 1,
+               dmi_s->dict_level + 1, dep->splid_end_split,
+               dmi_s->splstr_len + dep->ext_len,
+               spl_trie_->is_half_id(splid) ? 0 : dmi_s->all_full_id);
+
+    if (pos == c_phrase_.length - 1) {
+      lpi_items_[0].id = kLemmaIdComposing;
+      lpi_items_[0].psb = 0;  // 0 is bigger than normal lemma score.
+      lpi_total_ = 1;
+    }
+    return 1;
+  }
+  return 0;
+}
+
+size_t MatrixSearch::extend_mtrx_nd(MatrixNode *mtrx_nd, LmaPsbItem lpi_items[],
+                                    size_t lpi_num, PoolPosType dmi_fr,
+                                    size_t res_row) {
+  assert(NULL != mtrx_nd);
+  matrix_[res_row].mtrx_nd_fixed = NULL;
+
+  if (mtrx_nd_pool_used_ >= kMtrxNdPoolSize - kMaxNodeARow)
+    return 0;
+
+  if (0 == mtrx_nd->step) {
+    // Because the list is sorted, if the source step is 0, it is only
+    // necessary to pick up the first kMaxNodeARow items.
+    if (lpi_num > kMaxNodeARow)
+      lpi_num = kMaxNodeARow;
+  }
+
+  MatrixNode *mtrx_nd_res_min = mtrx_nd_pool_ + matrix_[res_row].mtrx_nd_pos;
+  for (size_t pos = 0; pos < lpi_num; pos++) {
+    float score = mtrx_nd->score + lpi_items[pos].psb;
+    if (pos > 0 && score - PRUMING_SCORE > mtrx_nd_res_min->score)
+      break;
+
+    // Try to add a new node
+    size_t mtrx_nd_num = matrix_[res_row].mtrx_nd_num;
+    MatrixNode *mtrx_nd_res = mtrx_nd_res_min + mtrx_nd_num;
+    bool replace = false;
+    // Find its position
+    while (mtrx_nd_res > mtrx_nd_res_min && score < (mtrx_nd_res - 1)->score) {
+      if (static_cast<size_t>(mtrx_nd_res - mtrx_nd_res_min) < kMaxNodeARow)
+        *mtrx_nd_res = *(mtrx_nd_res - 1);
+      mtrx_nd_res--;
+      replace = true;
+    }
+    if (replace || (mtrx_nd_num < kMaxNodeARow &&
+        matrix_[res_row].mtrx_nd_pos + mtrx_nd_num < kMtrxNdPoolSize)) {
+      mtrx_nd_res->id = lpi_items[pos].id;
+      mtrx_nd_res->score = score;
+      mtrx_nd_res->from = mtrx_nd;
+      mtrx_nd_res->dmi_fr = dmi_fr;
+      mtrx_nd_res->step = res_row;
+      if (matrix_[res_row].mtrx_nd_num < kMaxNodeARow)
+        matrix_[res_row].mtrx_nd_num++;
+    }
+  }
+  return matrix_[res_row].mtrx_nd_num;
+}
+
+PoolPosType MatrixSearch::match_dmi(size_t step_to, uint16 spl_ids[],
+                                    uint16 spl_id_num) {
+  if (pys_decoded_len_ < step_to || 0 == matrix_[step_to].dmi_num) {
+    return static_cast<PoolPosType>(-1);
+  }
+
+  for (PoolPosType dmi_pos = 0; dmi_pos < matrix_[step_to].dmi_num; dmi_pos++) {
+    DictMatchInfo *dmi = dmi_pool_ + matrix_[step_to].dmi_pos + dmi_pos;
+
+    if (dmi->dict_level != spl_id_num)
+      continue;
+
+    bool matched = true;
+    for (uint16 spl_pos = 0; spl_pos < spl_id_num; spl_pos++) {
+      if (spl_ids[spl_id_num - spl_pos - 1] != dmi->spl_id) {
+        matched = false;
+        break;
+      }
+
+      dmi = dmi_pool_ + dmi->dmi_fr;
+    }
+    if (matched) {
+      return matrix_[step_to].dmi_pos + dmi_pos;
+    }
+  }
+
+  return static_cast<PoolPosType>(-1);
+}
+
+char16* MatrixSearch::get_candidate0(char16 *cand_str, size_t max_len,
+                                     uint16 *retstr_len,
+                                     bool only_unfixed) {
+  if (pys_decoded_len_ == 0 ||
+      matrix_[pys_decoded_len_].mtrx_nd_num == 0)
+    return NULL;
+
+  LemmaIdType idxs[kMaxRowNum];
+  size_t id_num = 0;
+
+  MatrixNode *mtrx_nd = mtrx_nd_pool_ + matrix_[pys_decoded_len_].mtrx_nd_pos;
+
+  if (kPrintDebug0) {
+    printf("--- sentence score: %f\n", mtrx_nd->score);
+  }
+
+  if (kPrintDebug1) {
+    printf("==============Sentence DMI (reverse order) begin===========>>\n");
+  }
+
+  while (mtrx_nd != NULL) {
+    idxs[id_num] = mtrx_nd->id;
+    id_num++;
+
+    if (kPrintDebug1) {
+       printf("---MatrixNode [step: %d, lma_idx: %d, total score:%.5f]\n",
+              mtrx_nd->step, mtrx_nd->id, mtrx_nd->score);
+       debug_print_dmi(mtrx_nd->dmi_fr, 1);
+    }
+
+    mtrx_nd = mtrx_nd->from;
+  }
+
+  if (kPrintDebug1) {
+    printf("<<==============Sentence DMI (reverse order) end=============\n");
+  }
+
+  size_t ret_pos = 0;
+  do {
+    id_num--;
+    if (0 == idxs[id_num])
+      continue;
+
+    char16 str[kMaxLemmaSize + 1];
+    uint16 str_len = get_lemma_str(idxs[id_num], str, kMaxLemmaSize + 1);
+    if (str_len > 0 && max_len - ret_pos > str_len) {
+      if (!only_unfixed)
+        utf16_strncpy(cand_str + ret_pos, str, str_len);
+      else if (ret_pos >= fixed_hzs_)
+        utf16_strncpy(cand_str + ret_pos - fixed_hzs_, str, str_len);
+
+      ret_pos += str_len;
+    } else {
+      return NULL;
+    }
+  } while (id_num != 0);
+
+  if (!only_unfixed) {
+    if (NULL != retstr_len)
+      *retstr_len = ret_pos;
+    cand_str[ret_pos] = (char16)'\0';
+  } else {
+    if (NULL != retstr_len)
+      *retstr_len = ret_pos - fixed_hzs_;
+    cand_str[ret_pos - fixed_hzs_] = (char16)'\0';
+  }
+  return cand_str;
+}
+
+size_t MatrixSearch::get_lpis(const uint16* splid_str, size_t splid_str_len,
+                              LmaPsbItem* lma_buf, size_t max_lma_buf,
+                              const char16 *pfullsent, bool sort_by_psb) {
+  if (splid_str_len > kMaxLemmaSize)
+    return 0;
+
+  size_t num1 = dict_trie_->get_lpis(splid_str, splid_str_len,
+                                     lma_buf, max_lma_buf);
+  size_t num2 = 0;
+  if (NULL != user_dict_) {
+    num2 = user_dict_->get_lpis(splid_str, splid_str_len,
+                         lma_buf + num1, max_lma_buf - num1);
+  }
+
+  size_t num = num1 + num2;
+
+  if (0 == num)
+    return 0;
+
+  // Remove repeated items.
+  if (splid_str_len > 1) {
+    LmaPsbStrItem *lpsis = reinterpret_cast<LmaPsbStrItem*>(lma_buf + num);
+    size_t lpsi_num = (max_lma_buf - num) * sizeof(LmaPsbItem) /
+        sizeof(LmaPsbStrItem);
+    assert(lpsi_num > num);
+    if (num > lpsi_num) num = lpsi_num;
+    lpsi_num = num;
+
+    for (size_t pos = 0; pos < lpsi_num; pos++) {
+      lpsis[pos].lpi = lma_buf[pos];
+      get_lemma_str(lma_buf[pos].id, lpsis[pos].str, kMaxLemmaSize + 1);
+    }
+
+    myqsort(lpsis, lpsi_num, sizeof(LmaPsbStrItem), cmp_lpsi_with_str);
+
+    size_t remain_num = 0;
+    for (size_t pos = 0; pos < lpsi_num; pos++) {
+      if (pos > 0 && utf16_strcmp(lpsis[pos].str, lpsis[pos - 1].str) == 0) {
+        if (lpsis[pos].lpi.psb < lpsis[pos - 1].lpi.psb) {
+          assert(remain_num > 0);
+          lma_buf[remain_num - 1] = lpsis[pos].lpi;
+        }
+        continue;
+      }
+      if (NULL != pfullsent && utf16_strcmp(lpsis[pos].str, pfullsent) == 0)
+        continue;
+
+      lma_buf[remain_num] = lpsis[pos].lpi;
+      remain_num++;
+    }
+
+    // Update the result number
+    num = remain_num;
+  } else {
+    // For single character, some characters have more than one spelling, for
+    // example, "de" and "di" are all valid for a Chinese character, so when
+    // the user input  "d", repeated items are generated.
+    // For single character lemmas, Hanzis will be gotten
+    for (size_t pos = 0; pos < num; pos++) {
+      char16 hanzis[2];
+      get_lemma_str(lma_buf[pos].id, hanzis, 2);
+      lma_buf[pos].hanzi = hanzis[0];
+    }
+
+    myqsort(lma_buf, num, sizeof(LmaPsbItem), cmp_lpi_with_hanzi);
+
+    size_t remain_num = 0;
+    for (size_t pos = 0; pos < num; pos++) {
+      if (pos > 0 && lma_buf[pos].hanzi == lma_buf[pos - 1].hanzi) {
+        if (NULL != pfullsent &&
+            static_cast<char16>(0) == pfullsent[1] &&
+            lma_buf[pos].hanzi == pfullsent[0])
+          continue;
+
+        if (lma_buf[pos].psb < lma_buf[pos - 1].psb) {
+          assert(remain_num > 0);
+          assert(lma_buf[remain_num - 1].hanzi == lma_buf[pos].hanzi);
+          lma_buf[remain_num - 1] = lma_buf[pos];
+        }
+        continue;
+      }
+      if (NULL != pfullsent &&
+          static_cast<char16>(0) == pfullsent[1] &&
+          lma_buf[pos].hanzi == pfullsent[0])
+          continue;
+
+      lma_buf[remain_num] = lma_buf[pos];
+      remain_num++;
+    }
+
+    num = remain_num;
+  }
+
+  if (sort_by_psb) {
+    myqsort(lma_buf, num, sizeof(LmaPsbItem), cmp_lpi_with_psb);
+  }
+  return num;
+}
+
+uint16 MatrixSearch::get_lemma_str(LemmaIdType id_lemma, char16 *str_buf,
+                                   uint16 str_max) {
+  uint16 str_len = 0;
+
+  if (is_system_lemma(id_lemma)) {
+    str_len = dict_trie_->get_lemma_str(id_lemma, str_buf, str_max);
+  } else if (is_user_lemma(id_lemma)) {
+    if (NULL != user_dict_) {
+      str_len = user_dict_->get_lemma_str(id_lemma, str_buf, str_max);
+    } else {
+      str_len = 0;
+      str_buf[0] = static_cast<char16>('\0');
+    }
+  } else if (is_composing_lemma(id_lemma)) {
+    if (str_max <= 1)
+      return 0;
+    str_len = c_phrase_.sublma_start[c_phrase_.sublma_num];
+    if (str_len > str_max - 1)
+      str_len = str_max - 1;
+    utf16_strncpy(str_buf, c_phrase_.chn_str, str_len);
+    str_buf[str_len] = (char16)'\0';
+    return str_len;
+  }
+
+  return str_len;
+}
+
+uint16 MatrixSearch::get_lemma_splids(LemmaIdType id_lemma, uint16 *splids,
+                                      uint16 splids_max, bool arg_valid) {
+  uint16 splid_num = 0;
+
+  if (arg_valid) {
+    for (splid_num = 0; splid_num < splids_max; splid_num++) {
+      if (spl_trie_->is_half_id(splids[splid_num]))
+        break;
+    }
+    if (splid_num == splids_max)
+      return splid_num;
+  }
+
+  if (is_system_lemma(id_lemma)) {
+    splid_num = dict_trie_->get_lemma_splids(id_lemma, splids, splids_max,
+                                              arg_valid);
+  } else if (is_user_lemma(id_lemma)) {
+    if (NULL != user_dict_) {
+      splid_num = user_dict_->get_lemma_splids(id_lemma, splids, splids_max,
+                                               arg_valid);
+    } else {
+      splid_num = 0;
+    }
+  } else if (is_composing_lemma(id_lemma)) {
+    if (c_phrase_.length > splids_max) {
+      return 0;
+    }
+    for (uint16 pos = 0; pos < c_phrase_.length; pos++) {
+      splids[pos] = c_phrase_.spl_ids[pos];
+      if (spl_trie_->is_half_id(splids[pos])) {
+        return 0;
+      }
+    }
+  }
+  return splid_num;
+}
+
+size_t MatrixSearch::inner_predict(const char16 *fixed_buf, uint16 fixed_len,
+                                   char16 predict_buf[][kMaxPredictSize + 1],
+                                   size_t buf_len) {
+  size_t res_total = 0;
+  memset(npre_items_, 0, sizeof(NPredictItem) * npre_items_len_);
+  // In order to shorten the comments, j-character candidates predicted by
+  // i-character prefix are called P(i,j). All candiates predicted by
+  // i-character prefix are called P(i,*)
+  // Step 1. Get P(kMaxPredictSize, *) and sort them, here
+  // P(kMaxPredictSize, *) == P(kMaxPredictSize, 1)
+  for (size_t len = fixed_len; len >0; len--) {
+    // How many blank items are available
+    size_t this_max = npre_items_len_ - res_total;
+    size_t res_this;
+    // If the history is longer than 1, and we can not get prediction from
+    // lemmas longer than 2, in this case, we will add lemmas with
+    // highest scores as the prediction result.
+    if (fixed_len > 1 && 1 == len && 0 == res_total) {
+      // Try to find if recent n (n>1) characters can be a valid lemma in system
+      // dictionary.
+      bool nearest_n_word = false;
+      for (size_t nlen = 2; nlen <= fixed_len; nlen++) {
+        if (dict_trie_->get_lemma_id(fixed_buf + fixed_len - nlen, nlen) > 0) {
+          nearest_n_word = true;
+          break;
+        }
+      }
+      res_this = dict_trie_->predict_top_lmas(nearest_n_word ? len : 0,
+                                              npre_items_ + res_total,
+                                              this_max, res_total);
+      res_total += res_this;
+    }
+
+    // How many blank items are available
+    this_max = npre_items_len_ - res_total;
+    res_this = 0;
+    if (!kOnlyUserDictPredict) {
+      res_this =
+          dict_trie_->predict(fixed_buf + fixed_len - len, len,
+                              npre_items_ + res_total, this_max,
+                              res_total);
+    }
+
+    if (NULL != user_dict_) {
+      res_this = res_this +
+                 user_dict_->predict(fixed_buf + fixed_len - len, len,
+                                     npre_items_ + res_total + res_this,
+                                     this_max - res_this, res_total + res_this);
+    }
+
+    if (kPredictLimitGt1) {
+      myqsort(npre_items_ + res_total, res_this, sizeof(NPredictItem),
+              cmp_npre_by_score);
+
+      if (len > 3) {
+        if (res_this > kMaxPredictNumByGt3)
+          res_this = kMaxPredictNumByGt3;
+      } else if (3 == len) {
+        if (res_this > kMaxPredictNumBy3)
+          res_this = kMaxPredictNumBy3;
+      } else if (2 == len) {
+        if (res_this > kMaxPredictNumBy2)
+          res_this = kMaxPredictNumBy2;
+      }
+    }
+
+    res_total += res_this;
+  }
+
+  res_total = remove_duplicate_npre(npre_items_, res_total);
+
+  if (kPreferLongHistoryPredict) {
+    myqsort(npre_items_, res_total, sizeof(NPredictItem),
+            cmp_npre_by_hislen_score);
+  } else {
+    myqsort(npre_items_, res_total, sizeof(NPredictItem),
+            cmp_npre_by_score);
+  }
+
+  if (buf_len < res_total) {
+    res_total = buf_len;
+  }
+
+  if (kPrintDebug2) {
+    printf("/////////////////Predicted Items Begin////////////////////>>\n");
+    for (size_t i = 0; i < res_total; i++) {
+      printf("---");
+      for (size_t j = 0; j < kMaxPredictSize; j++) {
+        printf("%d  ", npre_items_[i].pre_hzs[j]);
+      }
+      printf("\n");
+    }
+    printf("<<///////////////Predicted Items End////////////////////////\n");
+  }
+
+  for (size_t i = 0; i < res_total; i++) {
+    utf16_strncpy(predict_buf[i], npre_items_[i].pre_hzs,
+                  kMaxPredictSize);
+    predict_buf[i][kMaxPredictSize] = '\0';
+  }
+
+  return res_total;
+}
+
+size_t MatrixSearch::get_predicts(const char16 fixed_buf[],
+                                  char16 predict_buf[][kMaxPredictSize + 1],
+                                  size_t buf_len) {
+  size_t fixed_len = utf16_strlen(fixed_buf);
+  if (0 ==fixed_len || fixed_len > kMaxPredictSize || 0 == buf_len)
+    return 0;
+
+  return inner_predict(fixed_buf, fixed_len, predict_buf, buf_len);
+}
+
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/mystdlib.cpp b/PinyinIME/jni/share/mystdlib.cpp
new file mode 100644
index 0000000..93bbcc9
--- /dev/null
+++ b/PinyinIME/jni/share/mystdlib.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+
+namespace ime_pinyin {
+
+// For debug purpose. You can add a fixed version of qsort and bsearch functions
+// here so that the output will be totally the same under different platforms.
+
+void myqsort(void *p, size_t n, size_t es,
+             int (*cmp)(const void *, const void *)) {
+  qsort(p,n, es, cmp);
+}
+
+void *mybsearch(const void *k, const void *b,
+                size_t n, size_t es,
+                int (*cmp)(const void *, const void *)) {
+  return bsearch(k, b, n, es, cmp);
+}
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/ngram.cpp b/PinyinIME/jni/share/ngram.cpp
new file mode 100644
index 0000000..d95477a
--- /dev/null
+++ b/PinyinIME/jni/share/ngram.cpp
@@ -0,0 +1,342 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include "../include/mystdlib.h"
+#include "../include/ngram.h"
+
+namespace ime_pinyin {
+
+#define ADD_COUNT 0.3
+
+int comp_double(const void *p1, const void *p2) {
+  if (*static_cast<const double*>(p1) < *static_cast<const double*>(p2))
+    return -1;
+  if (*static_cast<const double*>(p1) > *static_cast<const double*>(p2))
+    return 1;
+  return 0;
+}
+
+inline double distance(double freq, double code) {
+  // return fabs(freq - code);
+  return freq * fabs(log(freq) - log(code));
+}
+
+// Find the index of the code value which is nearest to the given freq
+int qsearch_nearest(double code_book[], double freq, int start, int end) {
+  if (start == end)
+    return start;
+
+  if (start + 1 == end) {
+    if (distance(freq, code_book[end]) > distance(freq, code_book[start]))
+      return start;
+    return end;
+  }
+
+  int mid = (start + end) / 2;
+
+  if (code_book[mid] > freq)
+    return qsearch_nearest(code_book, freq, start, mid);
+  else
+    return qsearch_nearest(code_book, freq, mid, end);
+}
+
+size_t update_code_idx(double freqs[], size_t num, double code_book[],
+                       CODEBOOK_TYPE *code_idx) {
+  size_t changed = 0;
+  for (size_t pos = 0; pos < num; pos++) {
+    CODEBOOK_TYPE idx;
+    idx = qsearch_nearest(code_book, freqs[pos], 0, kCodeBookSize - 1);
+    if (idx != code_idx[pos])
+      changed++;
+    code_idx[pos] = idx;
+  }
+  return changed;
+}
+
+double recalculate_kernel(double freqs[], size_t num, double code_book[],
+                          CODEBOOK_TYPE *code_idx) {
+  double ret = 0;
+
+  size_t *item_num =  new size_t[kCodeBookSize];
+  assert(item_num);
+  memset(item_num, 0, sizeof(size_t) * kCodeBookSize);
+
+  double *cb_new = new double[kCodeBookSize];
+  assert(cb_new);
+  memset(cb_new, 0, sizeof(double) * kCodeBookSize);
+
+  for (size_t pos = 0; pos < num; pos++) {
+    ret += distance(freqs[pos], code_book[code_idx[pos]]);
+
+    cb_new[code_idx[pos]] += freqs[pos];
+    item_num[code_idx[pos]] += 1;
+  }
+
+  for (size_t code = 0; code < kCodeBookSize; code++) {
+    assert(item_num[code] > 0);
+    code_book[code] = cb_new[code] / item_num[code];
+  }
+
+  delete [] item_num;
+  delete [] cb_new;
+
+  return ret;
+}
+
+void iterate_codes(double freqs[], size_t num, double code_book[],
+                   CODEBOOK_TYPE *code_idx) {
+  size_t iter_num = 0;
+  double delta_last = 0;
+  do {
+    size_t changed = update_code_idx(freqs, num, code_book, code_idx);
+
+    double delta = recalculate_kernel(freqs, num, code_book, code_idx);
+
+    if (kPrintDebug0) {
+      printf("---Unigram codebook iteration: %d : %d, %.9f\n",
+             iter_num, changed, delta);
+    }
+    iter_num++;
+
+    if (iter_num > 1 &&
+        (delta == 0 || fabs(delta_last - delta)/fabs(delta) < 0.000000001))
+      break;
+    delta_last = delta;
+  } while (true);
+}
+
+
+NGram* NGram::instance_ = NULL;
+
+NGram::NGram() {
+  initialized_ = false;
+  idx_num_ = 0;
+  lma_freq_idx_ = NULL;
+  sys_score_compensation_ = 0;
+
+#ifdef ___BUILD_MODEL___
+  freq_codes_df_ = NULL;
+#endif
+  freq_codes_ = NULL;
+}
+
+NGram::~NGram() {
+  if (NULL != lma_freq_idx_)
+    free(lma_freq_idx_);
+
+#ifdef ___BUILD_MODEL___
+  if (NULL != freq_codes_df_)
+    free(freq_codes_df_);
+#endif
+
+  if (NULL != freq_codes_)
+    free(freq_codes_);
+}
+
+NGram& NGram::get_instance() {
+  if (NULL == instance_)
+    instance_ = new NGram();
+  return *instance_;
+}
+
+bool NGram::save_ngram(FILE *fp) {
+  if (!initialized_ || NULL == fp)
+    return false;
+
+  if (0 == idx_num_ || NULL == freq_codes_ ||  NULL == lma_freq_idx_)
+    return false;
+
+  if (fwrite(&idx_num_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fwrite(freq_codes_, sizeof(LmaScoreType), kCodeBookSize, fp) !=
+      kCodeBookSize)
+    return false;
+
+  if (fwrite(lma_freq_idx_, sizeof(CODEBOOK_TYPE), idx_num_, fp) != idx_num_)
+    return false;
+
+  return true;
+}
+
+bool NGram::load_ngram(FILE *fp) {
+  if (NULL == fp)
+    return false;
+
+  initialized_ = false;
+
+  if (fread(&idx_num_, sizeof(size_t), 1, fp) != 1 )
+    return false;
+
+  if (NULL != lma_freq_idx_)
+    free(lma_freq_idx_);
+
+  if (NULL != freq_codes_)
+    free(freq_codes_);
+
+  lma_freq_idx_ = static_cast<CODEBOOK_TYPE*>
+                  (malloc(idx_num_ * sizeof(CODEBOOK_TYPE)));
+  freq_codes_ = static_cast<LmaScoreType*>
+      (malloc(kCodeBookSize * sizeof(LmaScoreType)));
+
+  if (NULL == lma_freq_idx_ || NULL == freq_codes_)
+    return false;
+
+  if (fread(freq_codes_, sizeof(LmaScoreType), kCodeBookSize, fp) !=
+      kCodeBookSize)
+    return false;
+
+  if (fread(lma_freq_idx_, sizeof(CODEBOOK_TYPE), idx_num_, fp) != idx_num_)
+    return false;
+
+  initialized_ = true;
+
+  total_freq_none_sys_ = 0;
+  return true;
+}
+
+void NGram::set_total_freq_none_sys(size_t freq_none_sys) {
+  total_freq_none_sys_ = freq_none_sys;
+  if (0 == total_freq_none_sys_) {
+    sys_score_compensation_ = 0;
+  } else {
+    double factor = static_cast<double>(total_freq_none_sys_) / (
+        kSysDictTotalFreq + total_freq_none_sys_);
+    sys_score_compensation_ = static_cast<float>(
+        log(factor) * kLogValueAmplifier);
+  }
+}
+
+// The caller makes sure this oject is initialized.
+float NGram::get_uni_psb(LemmaIdType lma_id) {
+  return  static_cast<float>(freq_codes_[lma_freq_idx_[lma_id]]) +
+      sys_score_compensation_;
+}
+
+float NGram::convert_psb_to_score(double psb) {
+  float score = static_cast<float>(
+      log(psb) * static_cast<double>(kLogValueAmplifier));
+  if (score > static_cast<float>(kMaxScore)) {
+    score = static_cast<float>(kMaxScore);
+  }
+  return score;
+}
+
+#ifdef ___BUILD_MODEL___
+bool NGram::build_unigram(LemmaEntry *lemma_arr, size_t lemma_num,
+                          LemmaIdType next_idx_unused) {
+  if (NULL == lemma_arr || 0 == lemma_num || next_idx_unused <= 1)
+    return false;
+
+  double total_freq = 0;
+  double *freqs = new double[next_idx_unused];
+  if (NULL == freqs)
+    return false;
+
+  freqs[0] = ADD_COUNT;
+  total_freq += freqs[0];
+  LemmaIdType idx_now = 0;
+  for (size_t pos = 0; pos < lemma_num; pos++) {
+    if (lemma_arr[pos].idx_by_hz == idx_now)
+      continue;
+    idx_now++;
+
+    assert(lemma_arr[pos].idx_by_hz == idx_now);
+
+    freqs[idx_now] = lemma_arr[pos].freq;
+    if (freqs[idx_now] <= 0)
+      freqs[idx_now] = 0.3;
+
+    total_freq += freqs[idx_now];
+  }
+
+  double max_freq = 0;
+  idx_num_ = idx_now + 1;
+  assert(idx_now + 1 == next_idx_unused);
+
+  for (size_t pos = 0; pos < idx_num_; pos++) {
+    freqs[pos] = freqs[pos] / total_freq;
+    assert(freqs[pos] > 0);
+    if (freqs[pos] > max_freq)
+      max_freq = freqs[pos];
+  }
+
+  // calculate the code book
+  if (NULL == freq_codes_df_)
+    freq_codes_df_ = new double[kCodeBookSize];
+  assert(freq_codes_df_);
+  memset(freq_codes_df_, 0, sizeof(double) * kCodeBookSize);
+
+  if (NULL == freq_codes_)
+    freq_codes_ = new LmaScoreType[kCodeBookSize];
+  assert(freq_codes_);
+  memset(freq_codes_, 0, sizeof(LmaScoreType) * kCodeBookSize);
+
+  size_t freq_pos = 0;
+  for (size_t code_pos = 0; code_pos < kCodeBookSize; code_pos++) {
+    bool found = true;
+
+    while (found) {
+      found = false;
+      double cand = freqs[freq_pos];
+      for (size_t i = 0; i < code_pos; i++)
+        if (freq_codes_df_[i] == cand) {
+          found = true;
+          break;
+        }
+      if (found)
+        freq_pos++;
+    }
+
+    freq_codes_df_[code_pos] = freqs[freq_pos];
+    freq_pos++;
+  }
+
+  myqsort(freq_codes_df_, kCodeBookSize, sizeof(double), comp_double);
+
+  if (NULL == lma_freq_idx_)
+    lma_freq_idx_ = new CODEBOOK_TYPE[idx_num_];
+  assert(lma_freq_idx_);
+
+  iterate_codes(freqs, idx_num_, freq_codes_df_, lma_freq_idx_);
+
+  delete [] freqs;
+
+  if (kPrintDebug0) {
+    printf("\n------Language Model Unigram Codebook------\n");
+  }
+
+  for (size_t code_pos = 0; code_pos < kCodeBookSize; code_pos++) {
+    double log_score = log(freq_codes_df_[code_pos]);
+    float final_score = convert_psb_to_score(freq_codes_df_[code_pos]);
+    if (kPrintDebug0) {
+      printf("code:%d, probability:%.9f, log score:%.3f, final score: %.3f\n",
+             code_pos, freq_codes_df_[code_pos], log_score, final_score);
+    }
+    freq_codes_[code_pos] = static_cast<LmaScoreType>(final_score);
+  }
+
+  initialized_ = true;
+  return true;
+}
+#endif
+
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/pinyinime.cpp b/PinyinIME/jni/share/pinyinime.cpp
new file mode 100644
index 0000000..550da7b
--- /dev/null
+++ b/PinyinIME/jni/share/pinyinime.cpp
@@ -0,0 +1,186 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include "../include/pinyinime.h"
+#include "../include/dicttrie.h"
+#include "../include/matrixsearch.h"
+#include "../include/spellingtrie.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  using namespace ime_pinyin;
+
+  // The maximum number of the prediction items.
+  static const size_t kMaxPredictNum = 500;
+
+  // Used to search Pinyin string and give the best candidate.
+  MatrixSearch* matrix_search = NULL;
+
+  char16 predict_buf[kMaxPredictNum][kMaxPredictSize + 1];
+
+  bool im_open_decoder(const char *fn_sys_dict, const char *fn_usr_dict) {
+    if (NULL != matrix_search)
+      delete matrix_search;
+
+    matrix_search = new MatrixSearch();
+    if (NULL == matrix_search) {
+      return false;
+    }
+
+    return matrix_search->init(fn_sys_dict, fn_usr_dict);
+  }
+
+  bool im_open_decoder_fd(int sys_fd, long start_offset, long length,
+                          const char *fn_usr_dict) {
+    if (NULL != matrix_search)
+      delete matrix_search;
+
+    matrix_search = new MatrixSearch();
+    if (NULL == matrix_search)
+      return false;
+
+    return matrix_search->init_fd(sys_fd, start_offset, length, fn_usr_dict);
+  }
+
+  void im_close_decoder() {
+    if (NULL != matrix_search) {
+      matrix_search->close();
+      delete matrix_search;
+    }
+    matrix_search = NULL;
+  }
+
+  void im_set_max_lens(size_t max_sps_len, size_t max_hzs_len) {
+    if (NULL != matrix_search) {
+      matrix_search->set_max_lens(max_sps_len, max_hzs_len);
+    }
+  }
+
+  void im_flush_cache() {
+    if (NULL != matrix_search)
+      matrix_search->flush_cache();
+  }
+
+  // To be updated.
+  size_t im_search(const char* pybuf, size_t pylen) {
+    if (NULL == matrix_search)
+      return 0;
+
+    matrix_search->search(pybuf, pylen);
+    return matrix_search->get_candidate_num();
+  }
+
+  size_t im_delsearch(size_t pos, bool is_pos_in_splid,
+                      bool clear_fixed_this_step) {
+    if (NULL == matrix_search)
+      return 0;
+    matrix_search->delsearch(pos, is_pos_in_splid, clear_fixed_this_step);
+    return matrix_search->get_candidate_num();
+  }
+
+  void im_reset_search() {
+    if (NULL == matrix_search)
+      return;
+
+    matrix_search->reset_search();
+  }
+
+  // To be removed
+  size_t im_add_letter(char ch) {
+    return 0;
+  }
+
+  const char* im_get_sps_str(size_t *decoded_len) {
+    if (NULL == matrix_search)
+      return NULL;
+
+    return matrix_search->get_pystr(decoded_len);
+  }
+
+  char16* im_get_candidate(size_t cand_id, char16* cand_str,
+                        size_t max_len) {
+    if (NULL == matrix_search)
+      return NULL;
+
+    return matrix_search->get_candidate(cand_id, cand_str, max_len);
+  }
+
+  size_t im_get_spl_start_pos(const uint16 *&spl_start) {
+    if (NULL == matrix_search)
+      return 0;
+
+    return matrix_search->get_spl_start(spl_start);
+  }
+
+  size_t im_choose(size_t choice_id) {
+    if (NULL == matrix_search)
+      return 0;
+
+    return matrix_search->choose(choice_id);
+  }
+
+  size_t im_cancel_last_choice() {
+    if (NULL == matrix_search)
+      return 0;
+
+    return matrix_search->cancel_last_choice();
+  }
+
+  size_t im_get_fixed_len() {
+    if (NULL == matrix_search)
+      return 0;
+
+    return matrix_search->get_fixedlen();
+  }
+
+  // To be removed
+  bool im_cancel_input() {
+    return true;
+  }
+
+
+  size_t im_get_predicts(const char16 *his_buf,
+                         char16 (*&pre_buf)[kMaxPredictSize + 1]) {
+    if (NULL == his_buf)
+      return 0;
+
+    size_t fixed_len = utf16_strlen(his_buf);
+    const char16 *fixed_ptr = his_buf;
+    if (fixed_len > kMaxPredictSize) {
+      fixed_ptr += fixed_len - kMaxPredictSize;
+      fixed_len = kMaxPredictSize;
+    }
+
+    pre_buf = predict_buf;
+    return matrix_search->get_predicts(his_buf, pre_buf, kMaxPredictNum);
+  }
+
+  void im_enable_shm_as_szm(bool enable) {
+    SpellingTrie &spl_trie = SpellingTrie::get_instance();
+    spl_trie.szm_enable_shm(enable);
+  }
+
+  void im_enable_ym_as_szm(bool enable) {
+    SpellingTrie &spl_trie = SpellingTrie::get_instance();
+    spl_trie.szm_enable_ym(enable);
+  }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/PinyinIME/jni/share/searchutility.cpp b/PinyinIME/jni/share/searchutility.cpp
new file mode 100644
index 0000000..281da38
--- /dev/null
+++ b/PinyinIME/jni/share/searchutility.cpp
@@ -0,0 +1,210 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include "../include/mystdlib.h"
+#include "../include/searchutility.h"
+
+namespace ime_pinyin {
+
+bool is_system_lemma(LemmaIdType lma_id) {
+  return (0 < lma_id && lma_id <= kSysDictIdEnd);
+}
+
+bool is_user_lemma(LemmaIdType lma_id) {
+  return (kUserDictIdStart <= lma_id && lma_id <= kUserDictIdEnd);
+}
+
+bool is_composing_lemma(LemmaIdType lma_id) {
+  return (kLemmaIdComposing == lma_id);
+}
+
+int cmp_lpi_with_psb(const void *p1, const void *p2) {
+  if ((static_cast<const LmaPsbItem*>(p1))->psb >
+      (static_cast<const LmaPsbItem*>(p2))->psb)
+    return 1;
+  if ((static_cast<const LmaPsbItem*>(p1))->psb <
+      (static_cast<const LmaPsbItem*>(p2))->psb)
+    return -1;
+  return 0;
+}
+
+int cmp_lpi_with_unified_psb(const void *p1, const void *p2) {
+  const LmaPsbItem *item1 = static_cast<const LmaPsbItem*>(p1);
+  const LmaPsbItem *item2 = static_cast<const LmaPsbItem*>(p2);
+
+  // The real unified psb is psb1 / lma_len1 and psb2 * lma_len2
+  // But we use psb1 * lma_len2 and psb2 * lma_len1 to get better
+  // precision.
+  size_t up1 = item1->psb * (item2->lma_len);
+  size_t up2 = item2->psb * (item1->lma_len);
+  if (up1 < up2) {
+    return -1;
+  }
+  if (up1 > up2) {
+    return 1;
+  }
+  return 0;
+}
+
+int cmp_lpi_with_id(const void *p1, const void *p2) {
+  if ((static_cast<const LmaPsbItem*>(p1))->id <
+      (static_cast<const LmaPsbItem*>(p2))->id)
+    return -1;
+  if ((static_cast<const LmaPsbItem*>(p1))->id >
+      (static_cast<const LmaPsbItem*>(p2))->id)
+    return 1;
+  return 0;
+}
+
+int cmp_lpi_with_hanzi(const void *p1, const void *p2) {
+  if ((static_cast<const LmaPsbItem*>(p1))->hanzi <
+      (static_cast<const LmaPsbItem*>(p2))->hanzi)
+    return -1;
+  if ((static_cast<const LmaPsbItem*>(p1))->hanzi >
+      (static_cast<const LmaPsbItem*>(p2))->hanzi)
+    return 1;
+
+  return 0;
+}
+
+int cmp_lpsi_with_str(const void *p1, const void *p2) {
+  return utf16_strcmp((static_cast<const LmaPsbStrItem*>(p1))->str,
+                      (static_cast<const LmaPsbStrItem*>(p2))->str);
+}
+
+
+int cmp_hanzis_1(const void *p1, const void *p2) {
+  if (*static_cast<const char16*>(p1) <
+      *static_cast<const char16*>(p2))
+    return -1;
+
+  if (*static_cast<const char16*>(p1) >
+      *static_cast<const char16*>(p2))
+    return 1;
+  return 0;
+}
+
+int cmp_hanzis_2(const void *p1, const void *p2) {
+  return  utf16_strncmp(static_cast<const char16*>(p1),
+                        static_cast<const char16*>(p2), 2);
+}
+
+int cmp_hanzis_3(const void *p1, const void *p2) {
+  return  utf16_strncmp(static_cast<const char16*>(p1),
+                        static_cast<const char16*>(p2), 3);
+}
+
+int cmp_hanzis_4(const void *p1, const void *p2) {
+  return  utf16_strncmp(static_cast<const char16*>(p1),
+                        static_cast<const char16*>(p2), 4);
+}
+
+int cmp_hanzis_5(const void *p1, const void *p2) {
+  return  utf16_strncmp(static_cast<const char16*>(p1),
+                        static_cast<const char16*>(p2), 5);
+}
+
+int cmp_hanzis_6(const void *p1, const void *p2) {
+  return  utf16_strncmp(static_cast<const char16*>(p1),
+                        static_cast<const char16*>(p2), 6);
+}
+
+int cmp_hanzis_7(const void *p1, const void *p2) {
+  return  utf16_strncmp(static_cast<const char16*>(p1),
+                        static_cast<const char16*>(p2), 7);
+}
+
+int cmp_hanzis_8(const void *p1, const void *p2) {
+  return  utf16_strncmp(static_cast<const char16*>(p1),
+                        static_cast<const char16*>(p2), 8);
+}
+
+int cmp_npre_by_score(const void *p1, const void *p2) {
+  if ((static_cast<const NPredictItem*>(p1))->psb >
+      (static_cast<const NPredictItem*>(p2))->psb)
+    return 1;
+
+  if ((static_cast<const NPredictItem*>(p1))->psb <
+      (static_cast<const NPredictItem*>(p2))->psb)
+    return -1;
+
+  return 0;
+}
+
+int cmp_npre_by_hislen_score(const void *p1, const void *p2) {
+  if ((static_cast<const NPredictItem*>(p1))->his_len <
+      (static_cast<const NPredictItem*>(p2))->his_len)
+    return 1;
+
+  if ((static_cast<const NPredictItem*>(p1))->his_len >
+      (static_cast<const NPredictItem*>(p2))->his_len)
+    return -1;
+
+  if ((static_cast<const NPredictItem*>(p1))->psb >
+      (static_cast<const NPredictItem*>(p2))->psb)
+    return 1;
+
+  if ((static_cast<const NPredictItem*>(p1))->psb <
+      (static_cast<const NPredictItem*>(p2))->psb)
+    return -1;
+
+  return 0;
+}
+
+int cmp_npre_by_hanzi_score(const void *p1, const void *p2) {
+  int ret_v = (utf16_strncmp((static_cast<const NPredictItem*>(p1))->pre_hzs,
+      (static_cast<const NPredictItem*>(p2))->pre_hzs, kMaxPredictSize));
+  if (0 != ret_v)
+    return ret_v;
+
+  if ((static_cast<const NPredictItem*>(p1))->psb >
+      (static_cast<const NPredictItem*>(p2))->psb)
+    return 1;
+
+  if ((static_cast<const NPredictItem*>(p1))->psb <
+      (static_cast<const NPredictItem*>(p2))->psb)
+    return -1;
+
+  return 0;
+}
+
+size_t remove_duplicate_npre(NPredictItem *npre_items, size_t npre_num) {
+  if (NULL == npre_items || 0 == npre_num)
+    return 0;
+
+  myqsort(npre_items, npre_num, sizeof(NPredictItem), cmp_npre_by_hanzi_score);
+
+  size_t remain_num = 1;  // The first one is reserved.
+  for (size_t pos = 1; pos < npre_num; pos++) {
+    if (utf16_strncmp(npre_items[pos].pre_hzs,
+                      npre_items[remain_num - 1].pre_hzs,
+                      kMaxPredictSize) != 0) {
+      if (remain_num != pos) {
+        npre_items[remain_num] = npre_items[pos];
+      }
+      remain_num++;
+    }
+  }
+  return remain_num;
+}
+
+size_t align_to_size_t(size_t size) {
+  size_t s = sizeof(size_t);
+  return (size + s -1) / s * s;
+}
+
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/spellingtable.cpp b/PinyinIME/jni/share/spellingtable.cpp
new file mode 100644
index 0000000..6005e20
--- /dev/null
+++ b/PinyinIME/jni/share/spellingtable.cpp
@@ -0,0 +1,313 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include "../include/spellingtable.h"
+
+namespace ime_pinyin {
+
+#ifdef ___BUILD_MODEL___
+
+const char SpellingTable::
+    kNotSupportList[kNotSupportNum][kMaxSpellingSize + 1] = {"HM", "HNG", "NG"};
+
+// "" is the biggest, so that all empty strings will be moved to the end
+// _eb mean empty is biggest
+int compare_raw_spl_eb(const void* p1, const void* p2) {
+  if ('\0' == (static_cast<const RawSpelling*>(p1))->str[0])
+    return 1;
+
+  if ('\0' == (static_cast<const RawSpelling*>(p2))->str[0])
+    return -1;
+
+  return strcmp((static_cast<const RawSpelling*>(p1))->str,
+                (static_cast<const RawSpelling*>(p2))->str);
+}
+
+size_t get_odd_next(size_t value) {
+  size_t v_next = value;
+  while (true) {
+    size_t v_next_sqrt = (size_t)sqrt(v_next);
+
+    bool is_odd = true;
+    for (size_t v_dv = 2; v_dv < v_next_sqrt + 1; v_dv++) {
+      if (v_next % v_dv == 0) {
+        is_odd = false;
+        break;
+      }
+    }
+
+    if (is_odd)
+      return v_next;
+
+    v_next++;
+  }
+
+  // never reach here
+  return 0;
+}
+
+SpellingTable::SpellingTable() {
+  need_score_ = false;
+  raw_spellings_ = NULL;
+  spelling_buf_ = NULL;
+  spelling_num_ = 0;
+  total_freq_ = 0;
+  frozen_ = true;
+}
+
+SpellingTable::~SpellingTable() {
+  free_resource();
+}
+
+size_t SpellingTable::get_hash_pos(const char* spelling_str) {
+  size_t hash_pos = 0;
+  for (size_t pos = 0; pos < spelling_size_; pos++) {
+    if ('\0' == spelling_str[pos])
+      break;
+    hash_pos += (size_t)spelling_str[pos];
+  }
+
+  hash_pos = hash_pos % spelling_max_num_;
+  return hash_pos;
+}
+
+size_t SpellingTable::hash_pos_next(size_t hash_pos) {
+  hash_pos += 123;
+  hash_pos = hash_pos % spelling_max_num_;
+  return hash_pos;
+}
+
+void SpellingTable::free_resource() {
+  if (NULL != raw_spellings_)
+    delete [] raw_spellings_;
+  raw_spellings_ = NULL;
+
+  if (NULL != spelling_buf_)
+    delete [] spelling_buf_;
+  spelling_buf_ = NULL;
+}
+
+bool SpellingTable::init_table(size_t pure_spl_size, size_t spl_max_num,
+                               bool need_score) {
+  if (pure_spl_size == 0 || spl_max_num ==0)
+    return false;
+
+  need_score_ = need_score;
+
+  free_resource();
+
+  spelling_size_ = pure_spl_size + 1;
+  if (need_score)
+    spelling_size_ += 1;
+  spelling_max_num_ = get_odd_next(spl_max_num);
+  spelling_num_ = 0;
+
+  raw_spellings_ = new RawSpelling[spelling_max_num_];
+  spelling_buf_ = new char[spelling_max_num_ * (spelling_size_)];
+  if (NULL == raw_spellings_ || NULL == spelling_buf_) {
+    free_resource();
+    return false;
+  }
+
+  memset(raw_spellings_, 0, spelling_max_num_ * sizeof(RawSpelling));
+  memset(spelling_buf_, 0, spelling_max_num_ * (spelling_size_));
+  frozen_ = false;
+  total_freq_ = 0;
+  return true;
+}
+
+bool SpellingTable::put_spelling(const char* spelling_str, double freq) {
+  if (frozen_ || NULL == spelling_str)
+    return false;
+
+  for (size_t pos = 0; pos < kNotSupportNum; pos++) {
+    if (strcmp(spelling_str, kNotSupportList[pos]) == 0) {
+      return false;
+    }
+  }
+
+  total_freq_ += freq;
+
+  size_t hash_pos = get_hash_pos(spelling_str);
+
+  raw_spellings_[hash_pos].str[spelling_size_ - 1] = '\0';
+
+  if (strncmp(raw_spellings_[hash_pos].str, spelling_str,
+              spelling_size_ - 1) == 0) {
+    raw_spellings_[hash_pos].freq += freq;
+    return true;
+  }
+
+  size_t hash_pos_ori = hash_pos;
+
+  while (true) {
+    if (strncmp(raw_spellings_[hash_pos].str,
+                spelling_str, spelling_size_ - 1) == 0) {
+      raw_spellings_[hash_pos].freq += freq;
+      return true;
+    }
+
+    if ('\0' == raw_spellings_[hash_pos].str[0]) {
+      raw_spellings_[hash_pos].freq += freq;
+      strncpy(raw_spellings_[hash_pos].str, spelling_str, spelling_size_ - 1);
+      raw_spellings_[hash_pos].str[spelling_size_ - 1] = '\0';
+      spelling_num_++;
+      return true;
+    }
+
+    hash_pos = hash_pos_next(hash_pos);
+    if (hash_pos_ori == hash_pos)
+      return false;
+  }
+
+  // never reach here
+  return false;
+}
+
+bool SpellingTable::contain(const char* spelling_str) {
+  if (NULL == spelling_str || NULL == spelling_buf_ || frozen_)
+    return false;
+
+  size_t hash_pos = get_hash_pos(spelling_str);
+
+  if ('\0' == raw_spellings_[hash_pos].str[0])
+    return false;
+
+  if (strncmp(raw_spellings_[hash_pos].str, spelling_str, spelling_size_ - 1)
+      == 0)
+    return true;
+
+  size_t hash_pos_ori = hash_pos;
+
+  while (true) {
+    hash_pos = hash_pos_next(hash_pos);
+    if (hash_pos_ori == hash_pos)
+      return false;
+
+    if ('\0' == raw_spellings_[hash_pos].str[0])
+      return false;
+
+    if (strncmp(raw_spellings_[hash_pos].str, spelling_str, spelling_size_ - 1)
+        == 0)
+      return true;
+  }
+
+  // never reach here
+  return false;
+}
+
+const char* SpellingTable::arrange(size_t *item_size, size_t *spl_num) {
+  if (NULL == raw_spellings_ || NULL == spelling_buf_ ||
+      NULL == item_size || NULL == spl_num)
+    return NULL;
+
+  qsort(raw_spellings_, spelling_max_num_, sizeof(RawSpelling),
+        compare_raw_spl_eb);
+
+  // After sorting, only the first spelling_num_ items are valid.
+  // Copy them to the destination buffer.
+  for (size_t pos = 0; pos < spelling_num_; pos++) {
+    strncpy(spelling_buf_ + pos * spelling_size_, raw_spellings_[pos].str,
+            spelling_size_);
+  }
+
+  if (need_score_) {
+    if (kPrintDebug0)
+      printf("------------Spelling Possiblities--------------\n");
+
+    double max_score = 0;
+    double min_score = 0;
+
+    // After sorting, only the first spelling_num_ items are valid.
+    for (size_t pos = 0; pos < spelling_num_; pos++) {
+      raw_spellings_[pos].freq /= total_freq_;
+      if (need_score_) {
+        if (0 == pos) {
+          max_score = raw_spellings_[0].freq;
+          min_score = max_score;
+        } else {
+          if (raw_spellings_[pos].freq > max_score)
+            max_score = raw_spellings_[pos].freq;
+          if (raw_spellings_[pos].freq < min_score)
+            min_score = raw_spellings_[pos].freq;
+        }
+      }
+    }
+
+    if (kPrintDebug0)
+      printf("-----max psb: %f, min psb: %f\n", max_score, min_score);
+
+    max_score = log(max_score);
+    min_score = log(min_score);
+
+    if (kPrintDebug0)
+      printf("-----max log value: %f, min log value: %f\n",
+             max_score, min_score);
+
+    // The absolute value of min_score is bigger than that of max_score because
+    // both of them are negative after log function.
+    score_amplifier_ = 1.0 * 255 / min_score;
+
+    double average_score = 0;
+    for (size_t pos = 0; pos < spelling_num_; pos++) {
+      double score = log(raw_spellings_[pos].freq) * score_amplifier_;
+      assert(score >= 0);
+
+      average_score += score;
+
+      // Because of calculation precision issue, score might be a little bigger
+      // than 255 after being amplified.
+      if (score > 255)
+        score = 255;
+      char *this_spl_buf = spelling_buf_ + pos * spelling_size_;
+      this_spl_buf[spelling_size_ - 1] =
+          static_cast<char>((unsigned char)score);
+
+      if (kPrintDebug0) {
+        printf("---pos:%d, %s, psb:%d\n", pos, this_spl_buf,
+               (unsigned char)this_spl_buf[spelling_size_ -1]);
+      }
+    }
+    average_score /= spelling_num_;
+    assert(average_score <= 255);
+    average_score_ = static_cast<uint8>(average_score);
+
+    if (kPrintDebug0)
+      printf("\n----Score Amplifier: %f, Average Score: %d\n", score_amplifier_,
+             average_score_);
+  }
+
+  *item_size = spelling_size_;
+  *spl_num = spelling_num_;
+  frozen_ = true;
+  return spelling_buf_;
+}
+
+float SpellingTable::get_score_amplifier() {
+  return static_cast<float>(score_amplifier_);
+}
+
+unsigned char SpellingTable::get_average_score() {
+  return average_score_;
+}
+
+#endif  // ___BUILD_MODEL___
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/spellingtrie.cpp b/PinyinIME/jni/share/spellingtrie.cpp
new file mode 100644
index 0000000..85be46b
--- /dev/null
+++ b/PinyinIME/jni/share/spellingtrie.cpp
@@ -0,0 +1,828 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include "../include/dictdef.h"
+
+#ifdef ___BUILD_MODEL___
+#include "../include/spellingtable.h"
+#endif
+
+#include "../include/spellingtrie.h"
+
+namespace ime_pinyin {
+
+SpellingTrie* SpellingTrie::instance_ = NULL;
+
+// z/c/s is for Zh/Ch/Sh
+const char SpellingTrie::kHalfId2Sc_[kFullSplIdStart + 1] =
+    "0ABCcDEFGHIJKLMNOPQRSsTUVWXYZz";
+
+// Bit 0 : is it a Shengmu char?
+// Bit 1 : is it a Yunmu char? (one char is a Yunmu)
+// Bit 2 : is it enabled in ShouZiMu(first char) mode?
+unsigned char SpellingTrie::char_flags_[] = {
+  // a    b      c     d     e     f     g
+  0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01,
+  // h    i     j      k     l     m    n
+  0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01,
+  // o    p     q      r     s     t
+  0x02, 0x01, 0x01, 0x01, 0x01, 0x01,
+  // u    v     w      x     y     z
+  0x00, 0x00, 0x01, 0x01, 0x01, 0x01
+};
+
+int compare_spl(const void* p1, const void* p2) {
+  return strcmp((const char*)(p1), (const char*)(p2));
+}
+
+SpellingTrie::SpellingTrie() {
+  spelling_buf_ = NULL;
+  spelling_size_ = 0;
+  spelling_num_ = 0;
+  spl_ym_ids_ = NULL;
+  splstr_queried_ = NULL;
+  splstr16_queried_ = NULL;
+  root_ = NULL;
+  dumb_node_ = NULL;
+  splitter_node_ = NULL;
+  instance_ = NULL;
+  ym_buf_ = NULL;
+  f2h_ = NULL;
+
+  szm_enable_shm(true);
+  szm_enable_ym(true);
+
+#ifdef ___BUILD_MODEL___
+  node_num_ = 0;
+#endif
+}
+
+SpellingTrie::~SpellingTrie() {
+  if (NULL != spelling_buf_)
+    delete [] spelling_buf_;
+
+  if (NULL != splstr_queried_)
+    delete [] splstr_queried_;
+
+  if (NULL != splstr16_queried_)
+    delete [] splstr16_queried_;
+
+  if (NULL != spl_ym_ids_)
+    delete [] spl_ym_ids_;
+
+  if (NULL != root_) {
+    free_son_trie(root_);
+    delete root_;
+  }
+
+  if (NULL != dumb_node_) {
+    delete [] dumb_node_;
+  }
+
+  if (NULL != splitter_node_) {
+    delete [] splitter_node_;
+  }
+
+  if (NULL != instance_) {
+    delete instance_;
+    instance_ = NULL;
+  }
+
+  if (NULL != ym_buf_)
+    delete [] ym_buf_;
+
+  if (NULL != f2h_)
+    delete [] f2h_;
+}
+
+bool SpellingTrie::if_valid_id_update(uint16 *splid) const {
+  if (NULL == splid || 0 == *splid)
+    return false;
+
+  if (*splid >= kFullSplIdStart)
+    return true;
+  if (*splid < kFullSplIdStart) {
+    char ch = kHalfId2Sc_[*splid];
+    if (ch > 'Z') {
+      return true;
+    } else {
+      if (szm_is_enabled(ch)) {
+        return true;
+      } else if (is_yunmu_char(ch)) {
+        assert(h2f_num_[*splid] > 0);
+        *splid = h2f_start_[*splid];
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool SpellingTrie::is_half_id(uint16 splid) const {
+  if (0 == splid || splid >= kFullSplIdStart)
+    return false;
+
+  return true;
+}
+
+bool SpellingTrie::is_full_id(uint16 splid) const {
+  if (splid < kFullSplIdStart || splid >= kFullSplIdStart + spelling_num_)
+    return false;
+  return true;
+}
+
+bool SpellingTrie::half_full_compatible(uint16 half_id, uint16 full_id) const {
+  uint16 half_fr_full = full_to_half(full_id);
+
+  if (half_fr_full == half_id)
+    return true;
+
+  // &~0x20 is used to conver the char to upper case.
+  // So that Zh/Ch/Sh(whose char is z/c/s) can be matched with Z/C/S.
+  char ch_f = (kHalfId2Sc_[half_fr_full] & (~0x20));
+  char ch_h = kHalfId2Sc_[half_id];
+  if (ch_f == ch_h)
+    return true;
+
+  return false;
+}
+
+bool SpellingTrie::is_half_id_yunmu(uint16 splid) const {
+  if (0 == splid || splid >= kFullSplIdStart)
+    return false;
+
+  char ch = kHalfId2Sc_[splid];
+  // If ch >= 'a', that means the half id is one of Zh/Ch/Sh
+  if (ch >= 'a') {
+    return false;
+  }
+
+  return char_flags_[ch - 'A'] & kHalfIdYunmuMask;
+}
+
+bool SpellingTrie::is_shengmu_char(char ch) const {
+  return char_flags_[ch - 'A'] & kHalfIdShengmuMask;
+}
+
+bool SpellingTrie::is_yunmu_char(char ch) const {
+  return char_flags_[ch - 'A'] & kHalfIdYunmuMask;
+}
+
+bool SpellingTrie::is_szm_char(char ch) const {
+  return is_shengmu_char(ch) || is_yunmu_char(ch);
+}
+
+bool SpellingTrie::szm_is_enabled(char ch) const {
+  return char_flags_[ch - 'A'] & kHalfIdSzmMask;
+}
+
+void SpellingTrie::szm_enable_shm(bool enable) {
+  if (enable) {
+    for (char ch = 'A'; ch <= 'Z'; ch++) {
+      if (is_shengmu_char(ch))
+        char_flags_[ch - 'A'] = char_flags_[ch - 'A'] | kHalfIdSzmMask;
+    }
+  } else {
+    for (char ch = 'A'; ch <= 'Z'; ch++) {
+      if (is_shengmu_char(ch))
+        char_flags_[ch - 'A'] = char_flags_[ch - 'A'] & (kHalfIdSzmMask ^ 0xff);
+    }
+  }
+}
+
+void SpellingTrie::szm_enable_ym(bool enable) {
+  if (enable) {
+    for (char ch = 'A'; ch <= 'Z'; ch++) {
+      if (is_yunmu_char(ch))
+        char_flags_[ch - 'A'] = char_flags_[ch - 'A'] | kHalfIdSzmMask;
+    }
+  } else {
+    for (char ch = 'A'; ch <= 'Z'; ch++) {
+      if (is_yunmu_char(ch))
+        char_flags_[ch - 'A'] = char_flags_[ch - 'A'] & (kHalfIdSzmMask ^ 0xff);
+    }
+  }
+}
+
+bool SpellingTrie::is_szm_enabled(char ch) const {
+  return char_flags_[ch - 'A'] & kHalfIdSzmMask;
+}
+
+const SpellingTrie* SpellingTrie::get_cpinstance() {
+  return &get_instance();
+}
+
+SpellingTrie& SpellingTrie::get_instance() {
+  if (NULL == instance_)
+    instance_ = new SpellingTrie();
+
+  return *instance_;
+}
+
+uint16 SpellingTrie::half2full_num(uint16 half_id) const {
+  if (NULL == root_ || half_id >= kFullSplIdStart)
+    return 0;
+  return h2f_num_[half_id];
+}
+
+uint16 SpellingTrie::half_to_full(uint16 half_id, uint16 *spl_id_start) const {
+  if (NULL == spl_id_start || NULL == root_ || half_id >= kFullSplIdStart)
+    return 0;
+
+  *spl_id_start = h2f_start_[half_id];
+  return h2f_num_[half_id];
+}
+
+uint16 SpellingTrie::full_to_half(uint16 full_id) const {
+  if (NULL == root_ || full_id < kFullSplIdStart ||
+      full_id > spelling_num_ + kFullSplIdStart)
+    return 0;
+
+  return f2h_[full_id - kFullSplIdStart];
+}
+
+void SpellingTrie::free_son_trie(SpellingNode* node) {
+  if (NULL == node)
+    return;
+
+  for (size_t pos = 0; pos < node->num_of_son; pos++) {
+    free_son_trie(node->first_son + pos);
+  }
+
+  if (NULL != node->first_son)
+    delete [] node->first_son;
+}
+
+bool SpellingTrie::construct(const char* spelling_arr, size_t item_size,
+                             size_t item_num, float score_amplifier,
+                             unsigned char average_score) {
+  if (spelling_arr == NULL)
+    return false;
+
+  memset(h2f_start_, 0, sizeof(uint16) * kFullSplIdStart);
+  memset(h2f_num_, 0, sizeof(uint16) * kFullSplIdStart);
+
+  // If the arr is the same as the buf, means this function is called by
+  // load_table(), the table data are ready; otherwise the array should be
+  // saved.
+  if (spelling_arr != spelling_buf_) {
+    if (NULL != spelling_buf_)
+      delete [] spelling_buf_;
+    spelling_buf_ = new char[item_size * item_num];
+    if (NULL == spelling_buf_)
+      return false;
+    memcpy(spelling_buf_, spelling_arr, sizeof(char) * item_size * item_num);
+  }
+
+  spelling_size_ = item_size;
+  spelling_num_ = item_num;
+
+  score_amplifier_ = score_amplifier;
+  average_score_ = average_score;
+
+  if (NULL != splstr_queried_)
+    delete [] splstr_queried_;
+  splstr_queried_ = new char[spelling_size_];
+  if (NULL == splstr_queried_)
+    return false;
+
+  if (NULL != splstr16_queried_)
+    delete [] splstr16_queried_;
+  splstr16_queried_ = new char16[spelling_size_];
+  if (NULL == splstr16_queried_)
+    return false;
+
+  // First, sort the buf to ensure they are in ascendant order
+  qsort(spelling_buf_, spelling_num_, spelling_size_, compare_spl);
+
+#ifdef ___BUILD_MODEL___
+  node_num_ = 1;
+#endif
+
+  root_ = new SpellingNode();
+  memset(root_, 0, sizeof(SpellingNode));
+
+  dumb_node_ = new SpellingNode();
+  memset(dumb_node_, 0, sizeof(SpellingNode));
+  dumb_node_->score = average_score_;
+
+  splitter_node_ = new SpellingNode();
+  memset(splitter_node_, 0, sizeof(SpellingNode));
+  splitter_node_->score = average_score_;
+
+  memset(level1_sons_, 0, sizeof(SpellingNode*) * kValidSplCharNum);
+
+  root_->first_son = construct_spellings_subset(0, spelling_num_, 0, root_);
+
+  // Root's score should be cleared.
+  root_->score = 0;
+
+  if (NULL == root_->first_son)
+    return false;
+
+  h2f_start_[0] = h2f_num_[0] = 0;
+
+  if (!build_f2h())
+    return false;
+
+#ifdef ___BUILD_MODEL___
+  if (kPrintDebug0) {
+    printf("---SpellingTrie Nodes: %d\n", node_num_);
+  }
+  return build_ym_info();
+#else
+  return true;
+#endif
+}
+
+#ifdef ___BUILD_MODEL___
+const char* SpellingTrie::get_ym_str(const char *spl_str) {
+  bool start_ZCS = false;
+  if (is_shengmu_char(*spl_str)) {
+    if ('Z' == *spl_str || 'C' == *spl_str || 'S' == *spl_str)
+      start_ZCS = true;
+    spl_str += 1;
+    if (start_ZCS && 'h' == *spl_str)
+      spl_str += 1;
+  }
+  return spl_str;
+}
+
+bool SpellingTrie::build_ym_info() {
+  bool sucess;
+  SpellingTable *spl_table = new SpellingTable();
+
+  sucess = spl_table->init_table(kMaxPinyinSize - 1, 2 * kMaxYmNum, false);
+  assert(sucess);
+
+  for (uint16 pos = 0; pos < spelling_num_; pos++) {
+    const char *spl_str = spelling_buf_ + spelling_size_ * pos;
+    spl_str = get_ym_str(spl_str);
+    if ('\0' != spl_str[0]) {
+      sucess = spl_table->put_spelling(spl_str, 0);
+      assert(sucess);
+    }
+  }
+
+  size_t ym_item_size;  // '\0' is included
+  size_t ym_num;
+  const char* ym_buf;
+  ym_buf = spl_table->arrange(&ym_item_size, &ym_num);
+
+  if (NULL != ym_buf_)
+    delete [] ym_buf_;
+  ym_buf_ = new char[ym_item_size * ym_num];
+  if (NULL == ym_buf_) {
+    delete spl_table;
+    return false;
+  }
+
+  memcpy(ym_buf_, ym_buf, sizeof(char) * ym_item_size * ym_num);
+  ym_size_ = ym_item_size;
+  ym_num_ = ym_num;
+
+  delete spl_table;
+
+  // Generate the maping from the spelling ids to the Yunmu ids.
+  if (spl_ym_ids_)
+    delete spl_ym_ids_;
+  spl_ym_ids_ = new uint8[spelling_num_ + kFullSplIdStart];
+  if (NULL == spl_ym_ids_)
+    return false;
+
+  memset(spl_ym_ids_, 0, sizeof(uint8) * (spelling_num_ + kFullSplIdStart));
+
+  for (uint16 id = 1; id < spelling_num_ + kFullSplIdStart; id++) {
+    const char *str = get_spelling_str(id);
+
+    str = get_ym_str(str);
+    if ('\0' != str[0]) {
+      uint8 ym_id = get_ym_id(str);
+      spl_ym_ids_[id] = ym_id;
+      assert(ym_id > 0);
+    } else {
+      spl_ym_ids_[id] = 0;
+    }
+  }
+  return true;
+}
+#endif
+
+SpellingNode* SpellingTrie::construct_spellings_subset(
+    size_t item_start, size_t item_end, size_t level, SpellingNode* parent) {
+  if (level >= spelling_size_ || item_end <= item_start || NULL == parent)
+    return NULL;
+
+  SpellingNode *first_son = NULL;
+  uint16 num_of_son = 0;
+  unsigned char min_son_score = 255;
+
+  const char *spelling_last_start = spelling_buf_ + spelling_size_ * item_start;
+  char char_for_node = spelling_last_start[level];
+  assert(char_for_node >= 'A' && char_for_node <= 'Z' ||
+         'h' == char_for_node);
+
+  // Scan the array to find how many sons
+  for (size_t i = item_start + 1; i < item_end; i++) {
+    const char *spelling_current = spelling_buf_ + spelling_size_ * i;
+    char char_current = spelling_current[level];
+    if (char_current != char_for_node) {
+      num_of_son++;
+      char_for_node = char_current;
+    }
+  }
+  num_of_son++;
+
+  // Allocate memory
+#ifdef ___BUILD_MODEL___
+  node_num_ += num_of_son;
+#endif
+  first_son = new SpellingNode[num_of_son];
+  memset(first_son, 0, sizeof(SpellingNode)*num_of_son);
+
+  // Now begin construct tree
+  size_t son_pos = 0;
+
+  spelling_last_start = spelling_buf_ + spelling_size_ * item_start;
+  char_for_node = spelling_last_start[level];
+
+  bool spelling_endable = true;
+  if (spelling_last_start[level + 1] != '\0')
+    spelling_endable = false;
+
+  size_t item_start_next = item_start;
+
+  for (size_t i = item_start + 1; i < item_end; i++) {
+    const char *spelling_current = spelling_buf_ + spelling_size_ * i;
+    char char_current = spelling_current[level];
+    assert(is_valid_spl_char(char_current));
+
+    if (char_current != char_for_node) {
+      // Construct a node
+      SpellingNode *node_current = first_son + son_pos;
+      node_current->char_this_node = char_for_node;
+
+      // For quick search in the first level
+      if (0 == level)
+        level1_sons_[char_for_node - 'A'] = node_current;
+
+      if (spelling_endable) {
+        node_current->spelling_idx = kFullSplIdStart + item_start_next;
+      }
+
+      if (spelling_last_start[level + 1] != '\0' || i - item_start_next > 1) {
+        size_t real_start = item_start_next;
+        if (spelling_last_start[level + 1] == '\0')
+          real_start++;
+
+        node_current->first_son =
+            construct_spellings_subset(real_start, i, level + 1,
+                                       node_current);
+
+        if (real_start == item_start_next + 1) {
+          uint16 score_this = static_cast<unsigned char>(
+              spelling_last_start[spelling_size_ - 1]);
+          if (score_this < node_current->score)
+            node_current->score = score_this;
+        }
+      } else {
+        node_current->first_son = NULL;
+        node_current->score = static_cast<unsigned char>(
+            spelling_last_start[spelling_size_ - 1]);
+      }
+
+      if (node_current->score < min_son_score)
+        min_son_score = node_current->score;
+
+      bool is_half = false;
+      if (level == 0 && is_szm_char(char_for_node)) {
+        node_current->spelling_idx =
+          static_cast<uint16>(char_for_node - 'A' + 1);
+
+        if (char_for_node > 'C')
+          node_current->spelling_idx++;
+        if (char_for_node > 'S')
+          node_current->spelling_idx++;
+
+        h2f_num_[node_current->spelling_idx] = i - item_start_next;
+        is_half = true;
+      } else if (level == 1 && char_for_node == 'h') {
+        char ch_level0 = spelling_last_start[0];
+        uint16 part_id = 0;
+        if (ch_level0 == 'C')
+          part_id = 'C' - 'A' + 1 + 1;
+        else if (ch_level0 == 'S')
+          part_id = 'S' - 'A' + 1 + 2;
+        else if (ch_level0 == 'Z')
+          part_id = 'Z' - 'A' + 1 + 3;
+        if (0 != part_id) {
+          node_current->spelling_idx = part_id;
+          h2f_num_[node_current->spelling_idx] = i - item_start_next;
+          is_half = true;
+        }
+      }
+
+      if (is_half) {
+        if (h2f_num_[node_current->spelling_idx] > 0)
+          h2f_start_[node_current->spelling_idx] =
+            item_start_next + kFullSplIdStart;
+        else
+          h2f_start_[node_current->spelling_idx] = 0;
+      }
+
+      // for next sibling
+      spelling_last_start = spelling_current;
+      char_for_node = char_current;
+      item_start_next = i;
+      spelling_endable = true;
+      if (spelling_current[level + 1] != '\0')
+        spelling_endable = false;
+
+      son_pos++;
+    }
+  }
+
+  // the last one
+  SpellingNode *node_current = first_son + son_pos;
+  node_current->char_this_node = char_for_node;
+
+  // For quick search in the first level
+  if (0 == level)
+    level1_sons_[char_for_node - 'A'] = node_current;
+
+  if (spelling_endable) {
+    node_current->spelling_idx = kFullSplIdStart + item_start_next;
+  }
+
+  if (spelling_last_start[level + 1] != '\0' ||
+      item_end - item_start_next > 1) {
+    size_t real_start = item_start_next;
+    if (spelling_last_start[level + 1] == '\0')
+      real_start++;
+
+    node_current->first_son =
+        construct_spellings_subset(real_start, item_end, level + 1,
+                                   node_current);
+
+    if (real_start == item_start_next + 1) {
+      uint16 score_this = static_cast<unsigned char>(
+          spelling_last_start[spelling_size_ - 1]);
+      if (score_this < node_current->score)
+        node_current->score = score_this;
+    }
+  } else {
+    node_current->first_son = NULL;
+    node_current->score = static_cast<unsigned char>(
+        spelling_last_start[spelling_size_ - 1]);
+  }
+
+  if (node_current->score < min_son_score)
+    min_son_score = node_current->score;
+
+  assert(son_pos + 1 == num_of_son);
+
+  bool is_half = false;
+  if (level == 0 && szm_is_enabled(char_for_node)) {
+    node_current->spelling_idx = static_cast<uint16>(char_for_node - 'A' + 1);
+
+    if (char_for_node > 'C')
+      node_current->spelling_idx++;
+    if (char_for_node > 'S')
+      node_current->spelling_idx++;
+
+    h2f_num_[node_current->spelling_idx] = item_end - item_start_next;
+    is_half = true;
+  } else if (level == 1 && char_for_node == 'h') {
+    char ch_level0 = spelling_last_start[0];
+    uint16 part_id = 0;
+    if (ch_level0 == 'C')
+      part_id = 'C' - 'A' + 1 + 1;
+    else if (ch_level0 == 'S')
+      part_id = 'S' - 'A' + 1 + 2;
+    else if (ch_level0 == 'Z')
+      part_id = 'Z' - 'A' + 1 + 3;
+    if (0 != part_id) {
+      node_current->spelling_idx = part_id;
+      h2f_num_[node_current->spelling_idx] = item_end - item_start_next;
+      is_half = true;
+    }
+  }
+  if (is_half) {
+    if (h2f_num_[node_current->spelling_idx] > 0)
+      h2f_start_[node_current->spelling_idx] =
+        item_start_next + kFullSplIdStart;
+    else
+      h2f_start_[node_current->spelling_idx] = 0;
+  }
+
+  parent->num_of_son = num_of_son;
+  parent->score = min_son_score;
+  return first_son;
+}
+
+bool SpellingTrie::save_spl_trie(FILE *fp) {
+  if (NULL == fp || NULL == spelling_buf_)
+    return false;
+
+  if (fwrite(&spelling_size_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fwrite(&spelling_num_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fwrite(&score_amplifier_, sizeof(float), 1, fp) != 1)
+    return false;
+
+  if (fwrite(&average_score_, sizeof(unsigned char), 1, fp) != 1)
+    return false;
+
+  if (fwrite(spelling_buf_, sizeof(char) * spelling_size_,
+             spelling_num_, fp) != spelling_num_)
+    return false;
+
+  return true;
+}
+
+bool SpellingTrie::load_spl_trie(FILE *fp) {
+  if (NULL == fp)
+    return false;
+
+  if (fread(&spelling_size_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fread(&spelling_num_, sizeof(size_t), 1, fp) != 1)
+    return false;
+
+  if (fread(&score_amplifier_, sizeof(float), 1, fp) != 1)
+    return false;
+
+  if (fread(&average_score_, sizeof(unsigned char), 1, fp) != 1)
+    return false;
+
+  if (NULL != spelling_buf_)
+    delete [] spelling_buf_;
+
+  spelling_buf_ = new char[spelling_size_ * spelling_num_];
+  if (NULL == spelling_buf_)
+    return false;
+
+  if (fread(spelling_buf_, sizeof(char) * spelling_size_,
+            spelling_num_, fp) != spelling_num_)
+    return false;
+
+  return construct(spelling_buf_, spelling_size_, spelling_num_,
+                   score_amplifier_, average_score_);
+}
+
+bool SpellingTrie::build_f2h() {
+  if (NULL != f2h_)
+    delete [] f2h_;
+  f2h_ = new uint16[spelling_num_];
+  if (NULL == f2h_)
+    return false;
+
+  for (uint16 hid = 0; hid < kFullSplIdStart; hid++) {
+    for (uint16 fid = h2f_start_[hid];
+         fid < h2f_start_[hid] + h2f_num_[hid]; fid++)
+      f2h_[fid - kFullSplIdStart] = hid;
+  }
+
+  return true;
+}
+
+size_t SpellingTrie::get_spelling_num() {
+  return spelling_num_;
+}
+
+uint8 SpellingTrie::get_ym_id(const char *ym_str) {
+  if (NULL == ym_str || NULL == ym_buf_)
+    return 0;
+
+  for (uint8 pos = 0; pos < ym_num_; pos++)
+    if (strcmp(ym_buf_ + ym_size_ * pos, ym_str) == 0)
+      return pos + 1;
+
+  return 0;
+}
+
+const char* SpellingTrie::get_spelling_str(uint16 splid) {
+  splstr_queried_[0] = '\0';
+
+  if (splid >= kFullSplIdStart) {
+    splid -= kFullSplIdStart;
+    snprintf(splstr_queried_, spelling_size_, "%s",
+             spelling_buf_ + splid * spelling_size_);
+  } else {
+    if (splid == 'C' - 'A' + 1 + 1) {
+      snprintf(splstr_queried_, spelling_size_, "%s", "Ch");
+    } else if (splid == 'S' - 'A' + 1 + 2) {
+      snprintf(splstr_queried_, spelling_size_, "%s", "Sh");
+    } else if (splid == 'Z' - 'A' + 1 + 3) {
+      snprintf(splstr_queried_, spelling_size_, "%s", "Zh");
+    } else {
+      if (splid > 'C' - 'A' + 1)
+        splid--;
+      if (splid > 'S' - 'A' + 1)
+        splid--;
+      splstr_queried_[0] = 'A' + splid - 1;
+      splstr_queried_[1] = '\0';
+    }
+  }
+  return splstr_queried_;
+}
+
+const char16* SpellingTrie::get_spelling_str16(uint16 splid) {
+  splstr16_queried_[0] = '\0';
+
+  if (splid >= kFullSplIdStart) {
+    splid -= kFullSplIdStart;
+    for (size_t pos = 0; pos < spelling_size_; pos++) {
+      splstr16_queried_[pos] = static_cast<char16>
+          (spelling_buf_[splid * spelling_size_ + pos]);
+    }
+  } else {
+    if (splid == 'C' - 'A' + 1 + 1) {
+      splstr16_queried_[0] = static_cast<char16>('C');
+      splstr16_queried_[1] = static_cast<char16>('h');
+      splstr16_queried_[2] = static_cast<char16>('\0');
+    } else if (splid == 'S' - 'A' + 1 + 2) {
+      splstr16_queried_[0] = static_cast<char16>('S');
+      splstr16_queried_[1] = static_cast<char16>('h');
+      splstr16_queried_[2] = static_cast<char16>('\0');
+    } else if (splid == 'Z' - 'A' + 1 + 3) {
+      splstr16_queried_[0] = static_cast<char16>('Z');
+      splstr16_queried_[1] = static_cast<char16>('h');
+      splstr16_queried_[2] = static_cast<char16>('\0');
+    } else {
+      if (splid > 'C' - 'A' + 1)
+        splid--;
+      if (splid > 'S' - 'A' + 1)
+        splid--;
+      splstr16_queried_[0] = 'A' + splid - 1;
+      splstr16_queried_[1] = '\0';
+    }
+  }
+  return splstr16_queried_;
+}
+
+size_t SpellingTrie::get_spelling_str16(uint16 splid, char16 *splstr16,
+                                        size_t splstr16_len) {
+  if (NULL == splstr16 || splstr16_len < kMaxPinyinSize + 1) return 0;
+
+  if (splid >= kFullSplIdStart) {
+    splid -= kFullSplIdStart;
+    for (size_t pos = 0; pos <= kMaxPinyinSize; pos++) {
+      splstr16[pos] = static_cast<char16>
+          (spelling_buf_[splid * spelling_size_ + pos]);
+      if (static_cast<char16>('\0') == splstr16[pos]) {
+        return pos;
+      }
+    }
+  } else {
+    if (splid == 'C' - 'A' + 1 + 1) {
+      splstr16[0] = static_cast<char16>('C');
+      splstr16[1] = static_cast<char16>('h');
+      splstr16[2] = static_cast<char16>('\0');
+      return 2;
+    } else if (splid == 'S' - 'A' + 1 + 2) {
+      splstr16[0] = static_cast<char16>('S');
+      splstr16[1] = static_cast<char16>('h');
+      splstr16[2] = static_cast<char16>('\0');
+      return 2;
+    } else if (splid == 'Z' - 'A' + 1 + 3) {
+      splstr16[0] = static_cast<char16>('Z');
+      splstr16[1] = static_cast<char16>('h');
+      splstr16[2] = static_cast<char16>('\0');
+      return 2;
+    } else {
+      if (splid > 'C' - 'A' + 1)
+        splid--;
+      if (splid > 'S' - 'A' + 1)
+        splid--;
+      splstr16[0] = 'A' + splid - 1;
+      splstr16[1] = '\0';
+      return 1;
+    }
+  }
+
+  // Not reachable.
+  return 0;
+}
+
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/splparser.cpp b/PinyinIME/jni/share/splparser.cpp
new file mode 100644
index 0000000..d75aec6
--- /dev/null
+++ b/PinyinIME/jni/share/splparser.cpp
@@ -0,0 +1,341 @@
+/*
+ * 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.
+ */
+
+#include <assert.h>
+#include "../include/splparser.h"
+
+namespace ime_pinyin {
+
+SpellingParser::SpellingParser() {
+  spl_trie_ = SpellingTrie::get_cpinstance();
+}
+
+bool SpellingParser::is_valid_to_parse(char ch) {
+  return SpellingTrie::is_valid_spl_char(ch);
+}
+
+uint16 SpellingParser::splstr_to_idxs(const char *splstr, uint16 str_len,
+                                      uint16 spl_idx[], uint16 start_pos[],
+                                      uint16 max_size, bool &last_is_pre) {
+  if (NULL == splstr || 0 == max_size || 0 == str_len)
+    return 0;
+
+  if (!SpellingTrie::is_valid_spl_char(splstr[0]))
+    return 0;
+
+  last_is_pre = false;
+
+  const SpellingNode *node_this = spl_trie_->root_;
+
+  uint16 str_pos = 0;
+  uint16 idx_num = 0;
+  if (NULL != start_pos)
+    start_pos[0] = 0;
+  bool last_is_splitter = false;
+
+  while (str_pos < str_len) {
+    char char_this = splstr[str_pos];
+    // all characters outside of [a, z] are considered as splitters
+    if (!SpellingTrie::is_valid_spl_char(char_this)) {
+      // test if the current node is endable
+      uint16 id_this = node_this->spelling_idx;
+      if (spl_trie_->if_valid_id_update(&id_this)) {
+        spl_idx[idx_num] = id_this;
+
+        idx_num++;
+        str_pos++;
+        if (NULL != start_pos)
+          start_pos[idx_num] = str_pos;
+        if (idx_num >= max_size)
+          return idx_num;
+
+        node_this = spl_trie_->root_;
+        last_is_splitter = true;
+        continue;
+      } else {
+        if (last_is_splitter) {
+          str_pos++;
+          if (NULL != start_pos)
+            start_pos[idx_num] = str_pos;
+          continue;
+        } else {
+          return idx_num;
+        }
+      }
+    }
+
+    last_is_splitter = false;
+
+    SpellingNode *found_son = NULL;
+
+    if (0 == str_pos) {
+      if (char_this >= 'a')
+        found_son = spl_trie_->level1_sons_[char_this - 'a'];
+      else
+        found_son = spl_trie_->level1_sons_[char_this - 'A'];
+    } else {
+      SpellingNode *first_son = node_this->first_son;
+      // Because for Zh/Ch/Sh nodes, they are the last in the buffer and
+      // frequently used, so we scan from the end.
+      for (int i = 0; i < node_this->num_of_son; i++) {
+        SpellingNode *this_son = first_son + i;
+        if (SpellingTrie::is_same_spl_char(
+            this_son->char_this_node, char_this)) {
+          found_son = this_son;
+          break;
+        }
+      }
+    }
+
+    // found, just move the current node pointer to the the son
+    if (NULL != found_son) {
+      node_this = found_son;
+    } else {
+      // not found, test if it is endable
+      uint16 id_this = node_this->spelling_idx;
+      if (spl_trie_->if_valid_id_update(&id_this)) {
+        // endable, remember the index
+        spl_idx[idx_num] = id_this;
+
+        idx_num++;
+        if (NULL != start_pos)
+          start_pos[idx_num] = str_pos;
+        if (idx_num >= max_size)
+          return idx_num;
+        node_this = spl_trie_->root_;
+        continue;
+      } else {
+        return idx_num;
+      }
+    }
+
+    str_pos++;
+  }
+
+  uint16 id_this = node_this->spelling_idx;
+  if (spl_trie_->if_valid_id_update(&id_this)) {
+    // endable, remember the index
+    spl_idx[idx_num] = id_this;
+
+    idx_num++;
+    if (NULL != start_pos)
+      start_pos[idx_num] = str_pos;
+  }
+
+  last_is_pre = !last_is_splitter;
+
+  return idx_num;
+}
+
+uint16 SpellingParser::splstr_to_idxs_f(const char *splstr, uint16 str_len,
+                                        uint16 spl_idx[], uint16 start_pos[],
+                                        uint16 max_size, bool &last_is_pre) {
+  uint16 idx_num = splstr_to_idxs(splstr, str_len, spl_idx, start_pos,
+                                  max_size, last_is_pre);
+  for (uint16 pos = 0; pos < idx_num; pos++) {
+    if (spl_trie_->is_half_id_yunmu(spl_idx[pos])) {
+      spl_trie_->half_to_full(spl_idx[pos], spl_idx + pos);
+      if (pos == idx_num - 1) {
+        last_is_pre = false;
+      }
+    }
+  }
+  return idx_num;
+}
+
+uint16 SpellingParser::splstr16_to_idxs(const char16 *splstr, uint16 str_len,
+                                        uint16 spl_idx[], uint16 start_pos[],
+                                        uint16 max_size, bool &last_is_pre) {
+  if (NULL == splstr || 0 == max_size || 0 == str_len)
+    return 0;
+
+  if (!SpellingTrie::is_valid_spl_char(splstr[0]))
+    return 0;
+
+  last_is_pre = false;
+
+  const SpellingNode *node_this = spl_trie_->root_;
+
+  uint16 str_pos = 0;
+  uint16 idx_num = 0;
+  if (NULL != start_pos)
+    start_pos[0] = 0;
+  bool last_is_splitter = false;
+
+  while (str_pos < str_len) {
+    char16 char_this = splstr[str_pos];
+    // all characters outside of [a, z] are considered as splitters
+    if (!SpellingTrie::is_valid_spl_char(char_this)) {
+      // test if the current node is endable
+      uint16 id_this = node_this->spelling_idx;
+      if (spl_trie_->if_valid_id_update(&id_this)) {
+        spl_idx[idx_num] = id_this;
+
+        idx_num++;
+        str_pos++;
+        if (NULL != start_pos)
+          start_pos[idx_num] = str_pos;
+        if (idx_num >= max_size)
+          return idx_num;
+
+        node_this = spl_trie_->root_;
+        last_is_splitter = true;
+        continue;
+      } else {
+        if (last_is_splitter) {
+          str_pos++;
+          if (NULL != start_pos)
+            start_pos[idx_num] = str_pos;
+          continue;
+        } else {
+          return idx_num;
+        }
+      }
+    }
+
+    last_is_splitter = false;
+
+    SpellingNode *found_son = NULL;
+
+    if (0 == str_pos) {
+      if (char_this >= 'a')
+        found_son = spl_trie_->level1_sons_[char_this - 'a'];
+      else
+        found_son = spl_trie_->level1_sons_[char_this - 'A'];
+    } else {
+      SpellingNode *first_son = node_this->first_son;
+      // Because for Zh/Ch/Sh nodes, they are the last in the buffer and
+      // frequently used, so we scan from the end.
+      for (int i = 0; i < node_this->num_of_son; i++) {
+        SpellingNode *this_son = first_son + i;
+        if (SpellingTrie::is_same_spl_char(
+            this_son->char_this_node, char_this)) {
+          found_son = this_son;
+          break;
+        }
+      }
+    }
+
+    // found, just move the current node pointer to the the son
+    if (NULL != found_son) {
+      node_this = found_son;
+    } else {
+      // not found, test if it is endable
+      uint16 id_this = node_this->spelling_idx;
+      if (spl_trie_->if_valid_id_update(&id_this)) {
+        // endable, remember the index
+        spl_idx[idx_num] = id_this;
+
+        idx_num++;
+        if (NULL != start_pos)
+          start_pos[idx_num] = str_pos;
+        if (idx_num >= max_size)
+          return idx_num;
+        node_this = spl_trie_->root_;
+        continue;
+      } else {
+        return idx_num;
+      }
+    }
+
+    str_pos++;
+  }
+
+  uint16 id_this = node_this->spelling_idx;
+  if (spl_trie_->if_valid_id_update(&id_this)) {
+    // endable, remember the index
+    spl_idx[idx_num] = id_this;
+
+    idx_num++;
+    if (NULL != start_pos)
+      start_pos[idx_num] = str_pos;
+  }
+
+  last_is_pre = !last_is_splitter;
+
+  return idx_num;
+}
+
+uint16 SpellingParser::splstr16_to_idxs_f(const char16 *splstr, uint16 str_len,
+                                          uint16 spl_idx[], uint16 start_pos[],
+                                          uint16 max_size, bool &last_is_pre) {
+  uint16 idx_num = splstr16_to_idxs(splstr, str_len, spl_idx, start_pos,
+                                    max_size, last_is_pre);
+  for (uint16 pos = 0; pos < idx_num; pos++) {
+    if (spl_trie_->is_half_id_yunmu(spl_idx[pos])) {
+      spl_trie_->half_to_full(spl_idx[pos], spl_idx + pos);
+      if (pos == idx_num - 1) {
+        last_is_pre = false;
+      }
+    }
+  }
+  return idx_num;
+}
+
+uint16 SpellingParser::get_splid_by_str(const char *splstr, uint16 str_len,
+                                        bool *is_pre) {
+  if (NULL == is_pre)
+    return 0;
+
+  uint16 spl_idx[2];
+  uint16 start_pos[3];
+
+  if (splstr_to_idxs(splstr, str_len, spl_idx, start_pos, 2, *is_pre) != 1)
+    return 0;
+
+  if (start_pos[1] != str_len)
+    return 0;
+  return spl_idx[0];
+}
+
+uint16 SpellingParser::get_splid_by_str_f(const char *splstr, uint16 str_len,
+                                          bool *is_pre) {
+  if (NULL == is_pre)
+    return 0;
+
+  uint16 spl_idx[2];
+  uint16 start_pos[3];
+
+  if (splstr_to_idxs(splstr, str_len, spl_idx, start_pos, 2, *is_pre) != 1)
+    return 0;
+
+  if (start_pos[1] != str_len)
+    return 0;
+  if (spl_trie_->is_half_id_yunmu(spl_idx[0])) {
+    spl_trie_->half_to_full(spl_idx[0], spl_idx);
+    *is_pre = false;
+  }
+
+  return spl_idx[0];
+}
+
+uint16 SpellingParser::get_splids_parallel(const char *splstr, uint16 str_len,
+    uint16 splidx[], uint16 max_size,
+    uint16 &full_id_num, bool &is_pre) {
+  if (max_size <= 0 || !is_valid_to_parse(splstr[0]))
+    return 0;
+
+  splidx[0] = get_splid_by_str(splstr, str_len, &is_pre);
+  full_id_num = 0;
+  if (0 != splidx[0]) {
+    if (splidx[0] >= kFullSplIdStart)
+      full_id_num = 1;
+    return 1;
+  }
+  return 0;
+}
+
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/sync.cpp b/PinyinIME/jni/share/sync.cpp
new file mode 100644
index 0000000..91e27b8
--- /dev/null
+++ b/PinyinIME/jni/share/sync.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#include "../include/sync.h"
+#include <assert.h>
+#include <string.h>
+
+#ifdef ___SYNC_ENABLED___
+
+namespace ime_pinyin {
+
+Sync::Sync()
+  : userdict_(NULL),
+    dictfile_(NULL),
+    last_count_(0) {
+};
+
+Sync::~Sync() {
+}
+
+
+bool Sync::begin(const char * filename) {
+  if (userdict_) {
+    finish();
+  }
+
+  if (!filename) {
+    return false;
+  }
+
+  dictfile_ = strdup(filename);
+  if (!dictfile_) {
+    return false;
+  }
+
+  userdict_ = new UserDict();
+  if (!userdict_) {
+    free(dictfile_);
+    dictfile_ = NULL;
+    return false;
+  }
+
+  if (userdict_->load_dict((const char*)dictfile_, kUserDictIdStart,
+                           kUserDictIdEnd) == false) {
+    delete userdict_;
+    userdict_ = NULL;
+    free(dictfile_);
+    dictfile_ = NULL;
+    return false;
+  }
+
+  userdict_->set_limit(kUserDictMaxLemmaCount, kUserDictMaxLemmaSize, kUserDictRatio);
+
+  return true;
+}
+
+int Sync::put_lemmas(char16 * lemmas, int len) {
+  return userdict_->put_lemmas_no_sync_from_utf16le_string(lemmas, len);
+}
+
+int Sync::get_lemmas(char16 * str, int size) {
+  return userdict_->get_sync_lemmas_in_utf16le_string_from_beginning(str, size, &last_count_);
+}
+
+int Sync::get_last_got_count() {
+  return last_count_;
+}
+
+int Sync::get_total_count() {
+  return userdict_->get_sync_count();
+}
+
+void Sync::clear_last_got() {
+  if (last_count_ < 0) {
+    return;
+  }
+  userdict_->clear_sync_lemmas(0, last_count_);
+  last_count_ = 0;
+}
+
+void Sync::finish() {
+  if (userdict_) {
+    userdict_->close_dict();
+    delete userdict_;
+    userdict_ = NULL;
+    free(dictfile_);
+    dictfile_ = NULL;
+    last_count_ = 0;
+  }
+}
+
+int Sync::get_capacity() {
+  UserDict::UserDictStat stat;
+  userdict_->state(&stat);
+  return stat.limit_lemma_count - stat.lemma_count;
+}
+
+}
+#endif
diff --git a/PinyinIME/jni/share/userdict.cpp b/PinyinIME/jni/share/userdict.cpp
new file mode 100644
index 0000000..5064296
--- /dev/null
+++ b/PinyinIME/jni/share/userdict.cpp
@@ -0,0 +1,2262 @@
+/*
+ * 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.
+ */
+
+#include "../include/userdict.h"
+#include "../include/splparser.h"
+#include "../include/ngram.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <cutils/log.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+#include <pthread.h>
+#include <math.h>
+
+namespace ime_pinyin {
+
+#ifdef ___DEBUG_PERF___
+static uint64 _ellapse_ = 0;
+static struct timeval _tv_start_, _tv_end_;
+#define DEBUG_PERF_BEGIN \
+    do { \
+      gettimeofday(&_tv_start_, NULL); \
+    } while(0)
+#define DEBUG_PERF_END \
+    do { \
+      gettimeofday(&_tv_end_, NULL); \
+      _ellapse_ = (_tv_end_.tv_sec - _tv_start_.tv_sec) * 1000000 + \
+                  (_tv_end_.tv_usec - _tv_start_.tv_usec); \
+    } while(0)
+#define LOGD_PERF(message) \
+    LOGD("PERFORMANCE[%s] %llu usec.", message, _ellapse_);
+#else
+#define DEBUG_PERF_BEGIN
+#define DEBUG_PERF_END
+#define LOGD_PERF(message)
+#endif
+
+// XXX File load and write are thread-safe by g_mutex_
+static pthread_mutex_t g_mutex_ = PTHREAD_MUTEX_INITIALIZER;
+static struct timeval g_last_update_ = {0, 0};
+
+inline uint32 UserDict::get_dict_file_size(UserDictInfo * info) {
+  return (4 + info->lemma_size + (info->lemma_count << 3)
+#ifdef ___PREDICT_ENABLED___
+          + (info->lemma_count << 2)
+#endif
+#ifdef ___SYNC_ENABLED___
+          + (info->sync_count << 2)
+#endif
+          + sizeof(*info));
+}
+
+inline LmaScoreType UserDict::translate_score(int raw_score) {
+  // 1) ori_freq: original user frequency
+  uint32 ori_freq = extract_score_freq(raw_score);
+  // 2) lmt_off: lmt index (week offset for example)
+  uint64 lmt_off = ((raw_score & 0xffff0000) >> 16);
+  if (kUserDictLMTBitWidth < 16) {
+    uint64 mask = ~(1 << kUserDictLMTBitWidth);
+    lmt_off &= mask;
+  }
+  // 3) now_off: current time index (current week offset for example)
+  // assuming load_time_ is around current time
+  uint64 now_off = load_time_.tv_sec;
+  now_off = (now_off - kUserDictLMTSince) / kUserDictLMTGranularity;
+  now_off = (now_off << (64 - kUserDictLMTBitWidth));
+  now_off = (now_off >> (64 - kUserDictLMTBitWidth));
+  // 4) factor: decide expand-factor
+  int delta = now_off - lmt_off;
+  if (delta > 4)
+    delta = 4;
+  int factor = 80 - (delta << 4);
+
+  double tf = (double)(dict_info_.total_nfreq + total_other_nfreq_);
+  return (LmaScoreType)(log((double)factor * (double)ori_freq / tf)
+                        * NGram::kLogValueAmplifier);
+}
+
+inline int UserDict::extract_score_freq(int raw_score) {
+  // Frequence stored in lowest 16 bits
+  int freq = (raw_score & 0x0000ffff);
+  return freq;
+}
+
+inline uint64 UserDict::extract_score_lmt(int raw_score) {
+  uint64 lmt = ((raw_score & 0xffff0000) >> 16);
+  if (kUserDictLMTBitWidth < 16) {
+    uint64 mask = ~(1 << kUserDictLMTBitWidth);
+    lmt &= mask;
+  }
+  lmt = lmt * kUserDictLMTGranularity + kUserDictLMTSince;
+  return lmt;
+}
+
+inline int UserDict::build_score(uint64 lmt, int freq) {
+  lmt = (lmt - kUserDictLMTSince) / kUserDictLMTGranularity;
+  lmt = (lmt << (64 - kUserDictLMTBitWidth));
+  lmt = (lmt >> (64 - kUserDictLMTBitWidth));
+  uint16 lmt16 = (uint16)lmt;
+  int s = freq;
+  s &= 0x0000ffff;
+  s = (lmt16 << 16) | s;
+  return s;
+}
+
+inline int64 UserDict::utf16le_atoll(uint16 *s, int len) {
+  int64 ret = 0;
+  if (len <= 0)
+    return ret;
+
+  int flag = 1;
+  const uint16 * endp = s + len;
+  if (*s == '-') {
+    flag = -1;
+    s++;
+  } else if (*s == '+') {
+    s++;
+  }
+
+  while (*s >= '0' && *s <= '9' && s < endp) {
+    ret += ret * 10 + (*s) - '0';
+    s++;
+  }
+  return ret * flag;
+}
+
+inline int UserDict::utf16le_lltoa(int64 v, uint16 *s, int size) {
+  if (!s || size <= 0)
+    return 0;
+  uint16 *endp = s + size;
+  int ret_len = 0;
+  if (v < 0) {
+    *(s++) = '-';
+    ++ret_len;
+    v *= -1;
+  }
+
+  uint16 *b = s;
+  while (s < endp && v != 0) {
+    *(s++) = '0' + (v % 10);
+    v = v / 10;
+    ++ret_len;
+  }
+
+  if (v != 0)
+    return 0;
+
+  --s;
+
+  while (b < s) {
+    *b = *s;
+    ++b, --s;
+  }
+
+  return ret_len;
+}
+
+inline void UserDict::set_lemma_flag(uint32 offset, uint8 flag) {
+  offset &= kUserDictOffsetMask;
+  lemmas_[offset] |= flag;
+}
+
+inline char UserDict::get_lemma_flag(uint32 offset) {
+  offset &= kUserDictOffsetMask;
+  return (char)(lemmas_[offset]);
+}
+
+inline char UserDict::get_lemma_nchar(uint32 offset) {
+  offset &= kUserDictOffsetMask;
+  return (char)(lemmas_[offset + 1]);
+}
+
+inline uint16 * UserDict::get_lemma_spell_ids(uint32 offset) {
+  offset &= kUserDictOffsetMask;
+  return (uint16 *)(lemmas_ + offset + 2);
+}
+
+inline uint16 * UserDict::get_lemma_word(uint32 offset) {
+  offset &= kUserDictOffsetMask;
+  uint8 nchar = get_lemma_nchar(offset);
+  return (uint16 *)(lemmas_ + offset + 2 + (nchar << 1));
+}
+
+inline LemmaIdType UserDict::get_max_lemma_id() {
+  // When a lemma is deleted, we don't not claim its id back for
+  // simplicity and performance
+  return start_id_ + dict_info_.lemma_count - 1;
+}
+
+inline bool UserDict::is_valid_lemma_id(LemmaIdType id) {
+  if (id >= start_id_ && id <= get_max_lemma_id())
+    return true;
+  return false;
+}
+
+inline bool UserDict::is_valid_state() {
+  if (state_ == USER_DICT_NONE)
+    return false;
+  return true;
+}
+
+UserDict::UserDict()
+    : start_id_(0),
+      version_(0),
+      lemmas_(NULL),
+      offsets_(NULL),
+      scores_(NULL),
+      ids_(NULL),
+#ifdef ___PREDICT_ENABLED___
+      predicts_(NULL),
+#endif
+#ifdef ___SYNC_ENABLED___
+      syncs_(NULL),
+      sync_count_size_(0),
+#endif
+      offsets_by_id_(NULL),
+      lemma_count_left_(0),
+      lemma_size_left_(0),
+      dict_file_(NULL),
+      state_(USER_DICT_NONE) {
+  memset(&dict_info_, 0, sizeof(dict_info_));
+  memset(&load_time_, 0, sizeof(load_time_));
+#ifdef ___CACHE_ENABLED___
+  cache_init();
+#endif
+}
+
+UserDict::~UserDict() {
+  close_dict();
+}
+
+bool UserDict::load_dict(const char *file_name, LemmaIdType start_id,
+                         LemmaIdType end_id) {
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_BEGIN;
+#endif
+  dict_file_ = strdup(file_name);
+  if (!dict_file_)
+    return false;
+
+  start_id_ = start_id;
+
+  if (false == validate(file_name) && false == reset(file_name)) {
+    goto error;
+  }
+  if (false == load(file_name, start_id)) {
+    goto error;
+  }
+
+  state_ = USER_DICT_SYNC;
+
+  gettimeofday(&load_time_, NULL);
+
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_END;
+  LOGD_PERF("load_dict");
+#endif
+  return true;
+ error:
+  free((void*)dict_file_);
+  start_id_ = 0;
+  return false;
+}
+
+bool UserDict::close_dict() {
+  if (state_ == USER_DICT_NONE)
+    return true;
+  if (state_ == USER_DICT_SYNC)
+    goto out;
+
+  // If dictionary is written back by others,
+  // we can not simply write back here
+  // To do a safe flush, we have to discard all newly added
+  // lemmas and try to reload dict file.
+  pthread_mutex_lock(&g_mutex_);
+  if (load_time_.tv_sec > g_last_update_.tv_sec ||
+    (load_time_.tv_sec == g_last_update_.tv_sec &&
+     load_time_.tv_usec > g_last_update_.tv_usec)) {
+    write_back();
+    gettimeofday(&g_last_update_, NULL);
+  }
+  pthread_mutex_unlock(&g_mutex_);
+
+ out:
+  free((void*)dict_file_);
+  free(lemmas_);
+  free(offsets_);
+  free(offsets_by_id_);
+  free(scores_);
+  free(ids_);
+#ifdef ___PREDICT_ENABLED___
+  free(predicts_);
+#endif
+
+  version_ = 0;
+  dict_file_ = NULL;
+  lemmas_ = NULL;
+#ifdef ___SYNC_ENABLED___
+  syncs_ = NULL;
+  sync_count_size_ = 0;
+#endif
+  offsets_ = NULL;
+  offsets_by_id_ = NULL;
+  scores_ = NULL;
+  ids_ = NULL;
+#ifdef ___PREDICT_ENABLED___
+  predicts_ = NULL;
+#endif
+
+  memset(&dict_info_, 0, sizeof(dict_info_));
+  lemma_count_left_ = 0;
+  lemma_size_left_ = 0;
+  state_ = USER_DICT_NONE;
+
+  return true;
+}
+
+size_t UserDict::number_of_lemmas() {
+  return dict_info_.lemma_count;
+}
+
+void UserDict::reset_milestones(uint16 from_step, MileStoneHandle from_handle) {
+  return;
+}
+
+MileStoneHandle UserDict::extend_dict(MileStoneHandle from_handle,
+                                      const DictExtPara *dep,
+                                      LmaPsbItem *lpi_items,
+                                      size_t lpi_max, size_t *lpi_num) {
+  if (is_valid_state() == false)
+    return 0;
+
+  bool need_extend = false;
+
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_BEGIN;
+#endif
+  *lpi_num = _get_lpis(dep->splids, dep->splids_extended + 1,
+                       lpi_items, lpi_max, &need_extend);
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_END;
+  LOGD_PERF("extend_dict");
+#endif
+  return ((*lpi_num > 0 || need_extend) ? 1 : 0);
+}
+
+int UserDict::is_fuzzy_prefix_spell_id(
+    const uint16 * id1, uint16 len1, const UserDictSearchable *searchable) {
+  if (len1 < searchable->splids_len)
+    return 0;
+
+  SpellingTrie &spl_trie = SpellingTrie::get_instance();
+  uint32 i = 0;
+  for (i = 0; i < searchable->splids_len; i++) {
+    const char py1 = *spl_trie.get_spelling_str(id1[i]);
+    uint16 off = 8 * (i % 4);
+    const char py2 = ((searchable->signature[i/4] & (0xff << off)) >> off);
+    if (py1 == py2)
+      continue;
+    return 0;
+  }
+  return 1;
+}
+
+int UserDict::fuzzy_compare_spell_id(
+    const uint16 * id1, uint16 len1, const UserDictSearchable *searchable) {
+  if (len1 < searchable->splids_len)
+    return -1;
+  if (len1 > searchable->splids_len)
+    return 1;
+
+  SpellingTrie &spl_trie = SpellingTrie::get_instance();
+  uint32 i = 0;
+  for (i = 0; i < len1; i++) {
+    const char py1 = *spl_trie.get_spelling_str(id1[i]);
+    uint16 off = 8 * (i % 4);
+    const char py2 = ((searchable->signature[i/4] & (0xff << off)) >> off);
+    if (py1 == py2)
+      continue;
+    if (py1 > py2)
+      return 1;
+    return -1;
+  }
+  return 0;
+}
+
+bool UserDict::is_prefix_spell_id(
+    const uint16 * fullids, uint16 fulllen,
+    const UserDictSearchable *searchable) {
+  if (fulllen < searchable->splids_len)
+    return false;
+
+  uint32 i = 0;
+  for (; i < searchable->splids_len; i++) {
+    uint16 start_id = searchable->splid_start[i];
+    uint16 count = searchable->splid_count[i];
+    if (fullids[i] >= start_id && fullids[i] < start_id + count)
+      continue;
+    else
+      return false;
+  }
+  return true;
+}
+
+bool UserDict::equal_spell_id(
+    const uint16 * fullids, uint16 fulllen,
+    const UserDictSearchable *searchable) {
+  if (fulllen != searchable->splids_len)
+    return false;
+
+  uint32 i = 0;
+  for (; i < fulllen; i++) {
+    uint16 start_id = searchable->splid_start[i];
+    uint16 count = searchable->splid_count[i];
+    if (fullids[i] >= start_id && fullids[i] < start_id + count)
+      continue;
+    else
+      return false;
+  }
+  return true;
+}
+
+int32 UserDict::locate_first_in_offsets(const UserDictSearchable * searchable) {
+  int32 begin = 0;
+  int32 end = dict_info_.lemma_count - 1;
+  int32 middle = -1;
+
+  int32 first_prefix = middle;
+  int32 last_matched = middle;
+
+  while (begin <= end) {
+    middle = (begin + end) >> 1;
+    uint32 offset = offsets_[middle];
+    uint8 nchar = get_lemma_nchar(offset);
+    const uint16 * splids = get_lemma_spell_ids(offset);
+    int cmp = fuzzy_compare_spell_id(splids, nchar, searchable);
+    int pre = is_fuzzy_prefix_spell_id(splids, nchar, searchable);
+
+    if (pre)
+      first_prefix = middle;
+
+    if (cmp < 0) {
+      begin = middle + 1;
+    } else if (cmp > 0) {
+      end = middle - 1;
+    } else {
+      end = middle - 1;
+      last_matched = middle;
+    }
+  }
+
+  return first_prefix;
+}
+
+void UserDict::prepare_locate(UserDictSearchable *searchable,
+                             const uint16 *splid_str,
+                             uint16 splid_str_len) {
+  searchable->splids_len = splid_str_len;
+  memset(searchable->signature, 0, sizeof(searchable->signature));
+
+  SpellingTrie &spl_trie = SpellingTrie::get_instance();
+  uint32 i = 0;
+  for (; i < splid_str_len; i++) {
+    if (spl_trie.is_half_id(splid_str[i])) {
+      searchable->splid_count[i] =
+          spl_trie.half_to_full(splid_str[i],
+                                &(searchable->splid_start[i]));
+    } else {
+      searchable->splid_count[i] = 1;
+      searchable->splid_start[i] = splid_str[i];
+    }
+    const unsigned char py = *spl_trie.get_spelling_str(splid_str[i]);
+    searchable->signature[i>>2] |= (py << (8 * (i % 4)));
+  }
+}
+
+size_t UserDict::get_lpis(const uint16 *splid_str, uint16 splid_str_len,
+                          LmaPsbItem *lpi_items, size_t lpi_max) {
+  return _get_lpis(splid_str, splid_str_len, lpi_items, lpi_max, NULL);
+}
+
+size_t UserDict::_get_lpis(const uint16 *splid_str,
+                           uint16 splid_str_len, LmaPsbItem *lpi_items,
+                           size_t lpi_max, bool * need_extend) {
+  bool tmp_extend;
+  if (!need_extend)
+    need_extend = &tmp_extend;
+
+  *need_extend = false;
+
+  if (is_valid_state() == false)
+    return 0;
+  if (lpi_max <= 0)
+    return 0;
+
+  if (0 == pthread_mutex_trylock(&g_mutex_)) {
+    if (load_time_.tv_sec < g_last_update_.tv_sec ||
+      (load_time_.tv_sec == g_last_update_.tv_sec &&
+       load_time_.tv_usec < g_last_update_.tv_usec)) {
+      // Others updated disk file, have to reload
+      pthread_mutex_unlock(&g_mutex_);
+      flush_cache();
+    } else {
+      pthread_mutex_unlock(&g_mutex_);
+    }
+  } else {
+  }
+
+  UserDictSearchable searchable;
+  prepare_locate(&searchable, splid_str, splid_str_len);
+
+  uint32 max_off = dict_info_.lemma_count;
+#ifdef ___CACHE_ENABLED___
+  int32 middle;
+  uint32 start, count;
+  bool cached = cache_hit(&searchable, &start, &count);
+  if (cached) {
+    middle = start;
+    max_off = start + count;
+  } else {
+    middle = locate_first_in_offsets(&searchable);
+    start = middle;
+  }
+#else
+  int32 middle = locate_first_in_offsets(&searchable);
+#endif
+
+  if (middle == -1) {
+#ifdef ___CACHE_ENABLED___
+    if (!cached)
+      cache_push(USER_DICT_MISS_CACHE, &searchable, 0, 0);
+#endif
+    return 0;
+  }
+
+  size_t lpi_current = 0;
+
+  bool fuzzy_break = false;
+  bool prefix_break = false;
+  while ((size_t)middle < max_off && !fuzzy_break && !prefix_break) {
+    if (lpi_current >= lpi_max)
+      break;
+    uint32 offset = offsets_[middle];
+    // Ignore deleted lemmas
+    if (offset & kUserDictOffsetFlagRemove) {
+      middle++;
+      continue;
+    }
+    uint8 nchar = get_lemma_nchar(offset);
+    uint16 * splids = get_lemma_spell_ids(offset);
+#ifdef ___CACHE_ENABLED___
+    if (!cached && 0 != fuzzy_compare_spell_id(splids, nchar, &searchable)) {
+#else
+    if (0 != fuzzy_compare_spell_id(splids, nchar, &searchable)) {
+#endif
+      fuzzy_break = true;
+    }
+
+    if (prefix_break == false) {
+      if (is_fuzzy_prefix_spell_id(splids, nchar, &searchable)) {
+        if (*need_extend == false &&
+            is_prefix_spell_id(splids, nchar, &searchable)) {
+          *need_extend = true;
+        }
+      } else {
+        prefix_break = true;
+      }
+    }
+
+    if (equal_spell_id(splids, nchar, &searchable) == true) {
+      lpi_items[lpi_current].psb = translate_score(scores_[middle]);
+      lpi_items[lpi_current].id = ids_[middle];
+      lpi_items[lpi_current].lma_len = nchar;
+      lpi_current++;
+    }
+    middle++;
+  }
+
+#ifdef ___CACHE_ENABLED___
+  if (!cached) {
+    count = middle - start;
+    cache_push(USER_DICT_CACHE, &searchable, start, count);
+  }
+#endif
+
+  return lpi_current;
+}
+
+uint16 UserDict::get_lemma_str(LemmaIdType id_lemma, char16* str_buf,
+                               uint16 str_max) {
+  if (is_valid_state() == false)
+    return 0;
+  if (is_valid_lemma_id(id_lemma) == false)
+    return 0;
+  uint32 offset = offsets_by_id_[id_lemma - start_id_];
+  uint8 nchar = get_lemma_nchar(offset);
+  char16 * str = get_lemma_word(offset);
+  uint16 m = nchar < str_max -1 ? nchar : str_max - 1;
+  int i = 0;
+  for (; i < m; i++) {
+    str_buf[i] = str[i];
+  }
+  str_buf[i] = 0;
+  return m;
+}
+
+uint16 UserDict::get_lemma_splids(LemmaIdType id_lemma, uint16 *splids,
+                                  uint16 splids_max, bool arg_valid) {
+  if (is_valid_lemma_id(id_lemma) == false)
+    return 0;
+  uint32 offset = offsets_by_id_[id_lemma - start_id_];
+  uint8 nchar = get_lemma_nchar(offset);
+  const uint16 * ids = get_lemma_spell_ids(offset);
+  int i = 0;
+  for (; i < nchar && i < splids_max; i++)
+    splids[i] = ids[i];
+  return i;
+}
+
+size_t UserDict::predict(const char16 last_hzs[], uint16 hzs_len,
+                         NPredictItem *npre_items, size_t npre_max,
+                         size_t b4_used) {
+  uint32 new_added = 0;
+#ifdef ___PREDICT_ENABLED___
+  int32 end = dict_info_.lemma_count - 1;
+  int j = locate_first_in_predicts((const uint16*)last_hzs, hzs_len);
+  if (j == -1)
+    return 0;
+
+  while (j <= end) {
+    uint32 offset = predicts_[j];
+    // Ignore deleted lemmas
+    if (offset & kUserDictOffsetFlagRemove) {
+      j++;
+      continue;
+    }
+    uint32 nchar = get_lemma_nchar(offset);
+    uint16 * words = get_lemma_word(offset);
+    uint16 * splids = get_lemma_spell_ids(offset);
+
+    if (nchar <= hzs_len) {
+      j++;
+      continue;
+    }
+
+    if (memcmp(words, last_hzs, hzs_len << 1) == 0) {
+      if (new_added >= npre_max) {
+        return new_added;
+      }
+      uint32 cpy_len =
+          (nchar < kMaxPredictSize ? (nchar << 1) : (kMaxPredictSize << 1))
+          - (hzs_len << 1);
+      npre_items[new_added].his_len = hzs_len;
+      npre_items[new_added].psb = get_lemma_score(words, splids, nchar);
+      memcpy(npre_items[new_added].pre_hzs, words + hzs_len, cpy_len);
+      if ((cpy_len >> 1) < kMaxPredictSize) {
+        npre_items[new_added].pre_hzs[cpy_len >> 1] = 0;
+      }
+      new_added++;
+    } else {
+      break;
+    }
+
+    j++;
+  }
+#endif
+  return new_added;
+}
+
+int32 UserDict::locate_in_offsets(char16 lemma_str[], uint16 splid_str[],
+                                  uint16 lemma_len) {
+  int32 max_off = dict_info_.lemma_count;
+
+  UserDictSearchable searchable;
+  prepare_locate(&searchable, splid_str, lemma_len);
+#ifdef ___CACHE_ENABLED___
+  int32 off;
+  uint32 start, count;
+  bool cached = load_cache(&searchable, &start, &count);
+  if (cached) {
+    off = start;
+    max_off = start + count;
+  } else {
+    off = locate_first_in_offsets(&searchable);
+    start = off;
+  }
+#else
+  int32 off = locate_first_in_offsets(&searchable);
+#endif
+
+  if (off == -1) {
+    return off;
+  }
+
+  while (off < max_off) {
+    uint32 offset = offsets_[off];
+    if (offset & kUserDictOffsetFlagRemove) {
+      off++;
+      continue;
+    }
+    uint16 * splids = get_lemma_spell_ids(offset);
+#ifdef ___CACHE_ENABLED___
+    if (!cached && 0 != fuzzy_compare_spell_id(splids, lemma_len, &searchable))
+      break;
+#else
+    if (0 != fuzzy_compare_spell_id(splids, lemma_len, &searchable))
+      break;
+#endif
+    if (equal_spell_id(splids, lemma_len, &searchable) == true) {
+      uint16 * str = get_lemma_word(offset);
+      uint32 i = 0;
+      for (i = 0; i < lemma_len; i++) {
+        if (str[i] == lemma_str[i])
+          continue;
+        break;
+      }
+      if (i < lemma_len) {
+        off++;
+        continue;
+      }
+#ifdef ___CACHE_ENABLED___
+      // No need to save_cache here, since current function is invoked by
+      // put_lemma. It's rarely possible for a user input same lemma twice.
+      // That means first time user type a new lemma, it is newly added into
+      // user dictionary, then it's possible that user type the same lemma
+      // again.
+      // Another reason save_cache can not be invoked here is this function
+      // aborts when lemma is found, and it never knows the count.
+#endif
+      return off;
+    }
+    off++;
+  }
+
+  return -1;
+}
+
+#ifdef ___PREDICT_ENABLED___
+uint32 UserDict::locate_where_to_insert_in_predicts(
+    const uint16 * words, int lemma_len) {
+  int32 begin = 0;
+  int32 end = dict_info_.lemma_count - 1;
+  int32 middle = end;
+
+  uint32 last_matched = middle;
+
+  while (begin <= end) {
+    middle = (begin + end) >> 1;
+    uint32 offset = offsets_[middle];
+    uint8 nchar = get_lemma_nchar(offset);
+    const uint16 * ws = get_lemma_word(offset);
+
+    uint32 minl = nchar < lemma_len ? nchar : lemma_len;
+    uint32 k = 0;
+    int cmp = 0;
+
+    for (; k < minl; k++) {
+      if (ws[k] < words[k]) {
+        cmp = -1;
+        break;
+      } else if (ws[k] > words[k]) {
+        cmp = 1;
+        break;
+      }
+    }
+    if (cmp == 0) {
+      if (nchar < lemma_len)
+        cmp = -1;
+      else if (nchar > lemma_len)
+        cmp = 1;
+    }
+
+    if (cmp < 0) {
+      begin = middle + 1;
+      last_matched = middle;
+    } else if (cmp > 0) {
+      end = middle - 1;
+    } else {
+      end = middle - 1;
+      last_matched = middle;
+    }
+  }
+
+  return last_matched;
+}
+
+int32 UserDict::locate_first_in_predicts(const uint16 * words, int lemma_len) {
+  int32 begin = 0;
+  int32 end = dict_info_.lemma_count - 1;
+  int32 middle = -1;
+
+  int32 last_matched = middle;
+
+  while (begin <= end) {
+    middle = (begin + end) >> 1;
+    uint32 offset = offsets_[middle];
+    uint8 nchar = get_lemma_nchar(offset);
+    const uint16 * ws = get_lemma_word(offset);
+
+    uint32 minl = nchar < lemma_len ? nchar : lemma_len;
+    uint32 k = 0;
+    int cmp = 0;
+
+    for (; k < minl; k++) {
+      if (ws[k] < words[k]) {
+        cmp = -1;
+        break;
+      } else if (ws[k] > words[k]) {
+        cmp = 1;
+        break;
+      }
+    }
+    if (cmp == 0) {
+      if (nchar >= lemma_len)
+        last_matched = middle;
+      if (nchar < lemma_len)
+        cmp = -1;
+      else if (nchar > lemma_len)
+        cmp = 1;
+    }
+
+    if (cmp < 0) {
+      begin = middle + 1;
+    } else if (cmp > 0) {
+      end = middle - 1;
+    } else {
+      end = middle - 1;
+    }
+  }
+
+  return last_matched;
+}
+
+#endif
+
+LemmaIdType UserDict::get_lemma_id(char16 lemma_str[], uint16 splids[],
+                                   uint16 lemma_len) {
+  int32 off = locate_in_offsets(lemma_str, splids, lemma_len);
+  if (off == -1) {
+    return 0;
+  }
+
+  return ids_[off];
+}
+
+LmaScoreType UserDict::get_lemma_score(LemmaIdType lemma_id) {
+  if (is_valid_state() == false)
+    return 0;
+  if (is_valid_lemma_id(lemma_id) == false)
+    return 0;
+
+  return translate_score(_get_lemma_score(lemma_id));
+}
+
+LmaScoreType UserDict::get_lemma_score(char16 lemma_str[], uint16 splids[],
+                                uint16 lemma_len) {
+  if (is_valid_state() == false)
+    return 0;
+  return translate_score(_get_lemma_score(lemma_str, splids, lemma_len));
+}
+
+int UserDict::_get_lemma_score(LemmaIdType lemma_id) {
+  if (is_valid_state() == false)
+    return 0;
+  if (is_valid_lemma_id(lemma_id) == false)
+    return 0;
+
+  uint32 offset = offsets_by_id_[lemma_id - start_id_];
+
+  uint32 nchar = get_lemma_nchar(offset);
+  uint16 * spl = get_lemma_spell_ids(offset);
+  uint16 * wrd = get_lemma_word(offset);
+
+  int32 off = locate_in_offsets(wrd, spl, nchar);
+  if (off == -1) {
+    return 0;
+  }
+
+  return scores_[off];
+}
+
+int UserDict::_get_lemma_score(char16 lemma_str[], uint16 splids[],
+                                uint16 lemma_len) {
+  if (is_valid_state() == false)
+    return 0;
+
+  int32 off = locate_in_offsets(lemma_str, splids, lemma_len);
+  if (off == -1) {
+    return 0;
+  }
+
+  return scores_[off];
+}
+
+#ifdef ___SYNC_ENABLED___
+void UserDict::remove_lemma_from_sync_list(uint32 offset) {
+  offset &= kUserDictOffsetMask;
+  uint32 i = 0;
+  for (; i < dict_info_.sync_count; i++) {
+    unsigned int off = (syncs_[i] & kUserDictOffsetMask);
+    if (off == offset)
+      break;
+  }
+  if (i < dict_info_.sync_count) {
+    syncs_[i] = syncs_[dict_info_.sync_count - 1];
+    dict_info_.sync_count--;
+  }
+}
+#endif
+
+#ifdef ___PREDICT_ENABLED___
+void UserDict::remove_lemma_from_predict_list(uint32 offset) {
+  offset &= kUserDictOffsetMask;
+  uint32 i = 0;
+  for (; i < dict_info_.lemma_count; i++) {
+    unsigned int off = (predicts_[i] & kUserDictOffsetMask);
+    if (off == offset) {
+      predicts_[i] |= kUserDictOffsetFlagRemove;
+      break;
+    }
+  }
+}
+#endif
+
+bool UserDict::remove_lemma_by_offset_index(int offset_index) {
+  if (is_valid_state() == false)
+    return 0;
+
+  int32 off = offset_index;
+  if (off == -1) {
+    return false;
+  }
+
+  uint32 offset = offsets_[off];
+  uint32 nchar = get_lemma_nchar(offset);
+
+  offsets_[off] |= kUserDictOffsetFlagRemove;
+
+#ifdef ___SYNC_ENABLED___
+  // Remove corresponding sync item
+  remove_lemma_from_sync_list(offset);
+#endif
+
+#ifdef ___PREDICT_ENABLED___
+  remove_lemma_from_predict_list(offset);
+#endif
+  dict_info_.free_count++;
+  dict_info_.free_size += (2 + (nchar << 2));
+
+  if (state_ < USER_DICT_OFFSET_DIRTY)
+    state_ = USER_DICT_OFFSET_DIRTY;
+  return true;
+}
+
+bool UserDict::remove_lemma(LemmaIdType lemma_id) {
+  if (is_valid_state() == false)
+    return 0;
+  if (is_valid_lemma_id(lemma_id) == false)
+    return false;
+  uint32 offset = offsets_by_id_[lemma_id - start_id_];
+
+  uint32 nchar = get_lemma_nchar(offset);
+  uint16 * spl = get_lemma_spell_ids(offset);
+  uint16 * wrd = get_lemma_word(offset);
+
+  int32 off = locate_in_offsets(wrd, spl, nchar);
+
+  return remove_lemma_by_offset_index(off);
+}
+
+void UserDict::flush_cache() {
+  LemmaIdType start_id = start_id_;
+  const char * file = strdup(dict_file_);
+  if (!file)
+    return;
+  close_dict();
+  load_dict(file, start_id, kUserDictIdEnd);
+  free((void*)file);
+#ifdef ___CACHE_ENABLED___
+  cache_init();
+#endif
+  return;
+}
+
+bool UserDict::reset(const char *file) {
+  FILE *fp = fopen(file, "w+");
+  if (!fp) {
+    return false;
+  }
+  uint32 version = kUserDictVersion;
+  size_t wred = fwrite(&version, 1, 4, fp);
+  UserDictInfo info;
+  memset(&info, 0, sizeof(info));
+  // By default, no limitation for lemma count and size
+  // thereby, reclaim_ratio is never used
+  wred += fwrite(&info, 1, sizeof(info), fp);
+  if (wred != sizeof(info) + sizeof(version)) {
+    fclose(fp);
+    unlink(file);
+    return false;
+  }
+  fclose(fp);
+  return true;
+}
+
+bool UserDict::validate(const char *file) {
+  // b is ignored in POSIX compatible os including Linux
+  // while b is important flag for Windows to specify binary mode
+  FILE *fp = fopen(file, "rb");
+  if (!fp) {
+    return false;
+  }
+
+  size_t size;
+  size_t readed;
+  uint32 version;
+  UserDictInfo dict_info;
+
+  // validate
+  int err = fseek(fp, 0, SEEK_END);
+  if (err) {
+    goto error;
+  }
+
+  size = ftell(fp);
+  if (size < 4 + sizeof(dict_info)) {
+    goto error;
+  }
+
+  err = fseek(fp, 0, SEEK_SET);
+  if (err) {
+    goto error;
+  }
+
+  readed = fread(&version, 1, sizeof(version), fp);
+  if (readed < sizeof(version)) {
+    goto error;
+  }
+  if (version != kUserDictVersion) {
+    goto error;
+  }
+
+  err = fseek(fp, -1 * sizeof(dict_info), SEEK_END);
+  if (err) {
+    goto error;
+  }
+
+  readed = fread(&dict_info, 1, sizeof(dict_info), fp);
+  if (readed != sizeof(dict_info)) {
+    goto error;
+  }
+
+  if (size != get_dict_file_size(&dict_info)) {
+    goto error;
+  }
+
+  fclose(fp);
+  return true;
+
+ error:
+  fclose(fp);
+  return false;
+}
+
+bool UserDict::load(const char *file, LemmaIdType start_id) {
+  if (0 != pthread_mutex_trylock(&g_mutex_)) {
+    return false;
+  }
+  // b is ignored in POSIX compatible os including Linux
+  // while b is important flag for Windows to specify binary mode
+  FILE *fp = fopen(file, "rb");
+  if (!fp) {
+    pthread_mutex_unlock(&g_mutex_);
+    return false;
+  }
+
+  size_t readed, toread;
+  UserDictInfo dict_info;
+  uint8 *lemmas = NULL;
+  uint32 *offsets = NULL;
+#ifdef ___SYNC_ENABLED___
+  uint32 *syncs = NULL;
+#endif
+  uint32 *scores = NULL;
+  uint32 *ids = NULL;
+  uint32 *offsets_by_id = NULL;
+#ifdef ___PREDICT_ENABLED___
+  uint32 *predicts = NULL;
+#endif
+  size_t i;
+  int err;
+
+  err = fseek(fp, -1 * sizeof(dict_info), SEEK_END);
+  if (err) goto error;
+
+  readed = fread(&dict_info, 1, sizeof(dict_info), fp);
+  if (readed != sizeof(dict_info)) goto error;
+
+  lemmas = (uint8 *)malloc(
+      dict_info.lemma_size +
+      (kUserDictPreAlloc * (2 + (kUserDictAverageNchar << 2))));
+
+  if (!lemmas) goto error;
+
+  offsets = (uint32 *)malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2);
+  if (!offsets) goto error;
+
+#ifdef ___PREDICT_ENABLED___
+  predicts = (uint32 *)malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2);
+  if (!predicts) goto error;
+#endif
+
+#ifdef ___SYNC_ENABLED___
+  syncs = (uint32 *)malloc((dict_info.sync_count + kUserDictPreAlloc) << 2);
+  if (!syncs) goto error;
+#endif
+
+  scores = (uint32 *)malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2);
+  if (!scores) goto error;
+
+  ids = (uint32 *)malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2);
+  if (!ids) goto error;
+
+  offsets_by_id = (uint32 *)malloc(
+      (dict_info.lemma_count + kUserDictPreAlloc) << 2);
+  if (!offsets_by_id) goto error;
+
+  err = fseek(fp, 4, SEEK_SET);
+  if (err) goto error;
+
+  readed = 0;
+  while (readed < dict_info.lemma_size && !ferror(fp) && !feof(fp)) {
+    readed += fread(lemmas + readed, 1, dict_info.lemma_size - readed, fp);
+  }
+  if (readed < dict_info.lemma_size)
+    goto error;
+
+  toread = (dict_info.lemma_count << 2);
+  readed = 0;
+  while (readed < toread && !ferror(fp) && !feof(fp)) {
+    readed += fread((((uint8*)offsets) + readed), 1, toread - readed, fp);
+  }
+  if (readed < toread)
+    goto error;
+
+#ifdef ___PREDICT_ENABLED___
+  toread = (dict_info.lemma_count << 2);
+  readed = 0;
+  while (readed < toread && !ferror(fp) && !feof(fp)) {
+    readed += fread((((uint8*)predicts) + readed), 1, toread - readed, fp);
+  }
+  if (readed < toread)
+    goto error;
+#endif
+
+  readed = 0;
+  while (readed < toread && !ferror(fp) && !feof(fp)) {
+    readed += fread((((uint8*)scores) + readed), 1, toread - readed, fp);
+  }
+  if (readed < toread)
+    goto error;
+
+#ifdef ___SYNC_ENABLED___
+  toread = (dict_info.sync_count << 2);
+  readed = 0;
+  while (readed < toread && !ferror(fp) && !feof(fp)) {
+    readed += fread((((uint8*)syncs) + readed), 1, toread - readed, fp);
+  }
+  if (readed < toread)
+    goto error;
+#endif
+
+  for (i = 0; i < dict_info.lemma_count; i++) {
+    ids[i] = start_id + i;
+    offsets_by_id[i] = offsets[i];
+  }
+
+  lemmas_ = lemmas;
+  offsets_ = offsets;
+#ifdef ___SYNC_ENABLED___
+  syncs_ = syncs;
+  sync_count_size_ = dict_info.sync_count + kUserDictPreAlloc;
+#endif
+  offsets_by_id_ = offsets_by_id;
+  scores_ = scores;
+  ids_ = ids;
+#ifdef ___PREDICT_ENABLED___
+  predicts_ = predicts;
+#endif
+  lemma_count_left_ = kUserDictPreAlloc;
+  lemma_size_left_ = kUserDictPreAlloc * (2 + (kUserDictAverageNchar << 2));
+  memcpy(&dict_info_, &dict_info, sizeof(dict_info));
+  state_ = USER_DICT_SYNC;
+
+  fclose(fp);
+
+  pthread_mutex_unlock(&g_mutex_);
+  return true;
+
+ error:
+  if (lemmas) free(lemmas);
+  if (offsets) free(offsets);
+#ifdef ___SYNC_ENABLED___
+  if (syncs) free(syncs);
+#endif
+  if (scores) free(scores);
+  if (ids) free(ids);
+  if (offsets_by_id) free(offsets_by_id);
+#ifdef ___PREDICT_ENABLED___
+  if (predicts) free(predicts);
+#endif
+  fclose(fp);
+  pthread_mutex_unlock(&g_mutex_);
+  return false;
+}
+
+void UserDict::write_back() {
+  // XXX write back is only allowed from close_dict due to thread-safe sake
+  if (state_ == USER_DICT_NONE || state_ == USER_DICT_SYNC)
+    return;
+  int fd = open(dict_file_, O_WRONLY);
+  if (fd == -1)
+    return;
+  switch (state_) {
+    case USER_DICT_DEFRAGMENTED:
+      write_back_all(fd);
+      break;
+    case USER_DICT_LEMMA_DIRTY:
+      write_back_lemma(fd);
+      break;
+    case USER_DICT_OFFSET_DIRTY:
+      write_back_offset(fd);
+      break;
+    case USER_DICT_SCORE_DIRTY:
+      write_back_score(fd);
+      break;
+#ifdef ___SYNC_ENABLED___
+    case USER_DICT_SYNC_DIRTY:
+      write_back_sync(fd);
+      break;
+#endif
+    default:
+      break;
+  }
+  // It seems truncate is not need on Linux, Windows except Mac
+  // I am doing it here anyway for safety.
+  off_t cur = lseek(fd, 0, SEEK_CUR);
+  ftruncate(fd, cur);
+  close(fd);
+  state_ = USER_DICT_SYNC;
+}
+
+#ifdef ___SYNC_ENABLED___
+void UserDict::write_back_sync(int fd) {
+  int err = lseek(fd, 4 + dict_info_.lemma_size
+                  + (dict_info_.lemma_count << 3)
+#ifdef ___PREDICT_ENABLED___
+                  + (dict_info_.lemma_count << 2)
+#endif
+                  , SEEK_SET);
+  if (err == -1)
+    return;
+  write(fd, syncs_, dict_info_.sync_count << 2);
+  write(fd, &dict_info_, sizeof(dict_info_));
+}
+#endif
+
+void UserDict::write_back_offset(int fd) {
+  int err = lseek(fd, 4 + dict_info_.lemma_size, SEEK_SET);
+  if (err == -1)
+    return;
+  write(fd, offsets_, dict_info_.lemma_count << 2);
+#ifdef ___PREDICT_ENABLED___
+  write(fd, predicts_, dict_info_.lemma_count << 2);
+#endif
+  write(fd, scores_, dict_info_.lemma_count << 2);
+#ifdef ___SYNC_ENABLED___
+  write(fd, syncs_, dict_info_.sync_count << 2);
+#endif
+  write(fd, &dict_info_, sizeof(dict_info_));
+}
+
+void UserDict::write_back_score(int fd) {
+  int err = lseek(fd, 4 + dict_info_.lemma_size
+                  + (dict_info_.lemma_count << 2)
+#ifdef ___PREDICT_ENABLED___
+                  + (dict_info_.lemma_count << 2)
+#endif
+                  , SEEK_SET);
+  if (err == -1)
+    return;
+  write(fd, scores_, dict_info_.lemma_count << 2);
+#ifdef ___SYNC_ENABLED___
+  write(fd, syncs_, dict_info_.sync_count << 2);
+#endif
+  write(fd, &dict_info_, sizeof(dict_info_));
+}
+
+void UserDict::write_back_lemma(int fd) {
+  int err = lseek(fd, 4, SEEK_SET);
+  if (err == -1)
+    return;
+  // New lemmas are always appended, no need to write whole lemma block
+  size_t need_write = kUserDictPreAlloc *
+      (2 + (kUserDictAverageNchar << 2)) - lemma_size_left_;
+  err = lseek(fd, dict_info_.lemma_size - need_write, SEEK_CUR);
+  if (err == -1)
+    return;
+  write(fd, lemmas_ + dict_info_.lemma_size - need_write, need_write);
+
+  write(fd, offsets_,  dict_info_.lemma_count << 2);
+#ifdef ___PREDICT_ENABLED___
+  write(fd, predicts_,  dict_info_.lemma_count << 2);
+#endif
+  write(fd, scores_, dict_info_.lemma_count << 2);
+#ifdef ___SYNC_ENABLED___
+  write(fd, syncs_, dict_info_.sync_count << 2);
+#endif
+  write(fd, &dict_info_, sizeof(dict_info_));
+}
+
+void UserDict::write_back_all(int fd) {
+  // XXX lemma_size is handled differently in writeall
+  // and writelemma. I update lemma_size and lemma_count in different
+  // places for these two cases. Should fix it to make it consistent.
+  int err = lseek(fd, 4, SEEK_SET);
+  if (err == -1)
+    return;
+  write(fd, lemmas_, dict_info_.lemma_size);
+  write(fd, offsets_, dict_info_.lemma_count << 2);
+#ifdef ___PREDICT_ENABLED___
+  write(fd, predicts_, dict_info_.lemma_count << 2);
+#endif
+  write(fd, scores_, dict_info_.lemma_count << 2);
+#ifdef ___SYNC_ENABLED___
+  write(fd, syncs_, dict_info_.sync_count << 2);
+#endif
+  write(fd, &dict_info_, sizeof(dict_info_));
+}
+
+#ifdef ___CACHE_ENABLED___
+bool UserDict::load_cache(UserDictSearchable *searchable,
+                          uint32 *offset, uint32 *length) {
+  UserDictCache *cache = &caches_[searchable->splids_len - 1];
+  if (cache->head == cache->tail)
+    return false;
+
+  uint16 j, sig_len = kMaxLemmaSize / 4;
+  uint16 i = cache->head;
+  while (1) {
+    j = 0;
+    for (; j < sig_len; j++) {
+      if (cache->signatures[i][j] != searchable->signature[j])
+        break;
+    }
+    if (j < sig_len) {
+      i++;
+      if (i >= kUserDictCacheSize)
+        i -= kUserDictCacheSize;
+      if (i == cache->tail)
+        break;
+      continue;
+    }
+    *offset = cache->offsets[i];
+    *length = cache->lengths[i];
+    return true;
+  }
+  return false;
+}
+
+void UserDict::save_cache(UserDictSearchable *searchable,
+                          uint32 offset, uint32 length) {
+  UserDictCache *cache = &caches_[searchable->splids_len - 1];
+  uint16 next = cache->tail;
+
+  cache->offsets[next] = offset;
+  cache->lengths[next] = length;
+  uint16 sig_len = kMaxLemmaSize / 4;
+  uint16 j = 0;
+  for (; j < sig_len; j++) {
+    cache->signatures[next][j] = searchable->signature[j];
+  }
+
+  if (++next >= kUserDictCacheSize) {
+    next -= kUserDictCacheSize;
+  }
+  if (next == cache->head) {
+    cache->head++;
+    if (cache->head >= kUserDictCacheSize) {
+      cache->head -= kUserDictCacheSize;
+    }
+  }
+  cache->tail = next;
+}
+
+void UserDict::reset_cache() {
+  memset(caches_, 0, sizeof(caches_));
+}
+
+bool UserDict::load_miss_cache(UserDictSearchable *searchable) {
+  UserDictMissCache *cache = &miss_caches_[searchable->splids_len - 1];
+  if (cache->head == cache->tail)
+    return false;
+
+  uint16 j, sig_len = kMaxLemmaSize / 4;
+  uint16 i = cache->head;
+  while (1) {
+    j = 0;
+    for (; j < sig_len; j++) {
+      if (cache->signatures[i][j] != searchable->signature[j])
+        break;
+    }
+    if (j < sig_len) {
+      i++;
+      if (i >= kUserDictMissCacheSize)
+        i -= kUserDictMissCacheSize;
+      if (i == cache->tail)
+        break;
+      continue;
+    }
+    return true;
+  }
+  return false;
+}
+
+void UserDict::save_miss_cache(UserDictSearchable *searchable) {
+  UserDictMissCache *cache = &miss_caches_[searchable->splids_len - 1];
+  uint16 next = cache->tail;
+
+  uint16 sig_len = kMaxLemmaSize / 4;
+  uint16 j = 0;
+  for (; j < sig_len; j++) {
+    cache->signatures[next][j] = searchable->signature[j];
+  }
+
+  if (++next >= kUserDictMissCacheSize) {
+    next -= kUserDictMissCacheSize;
+  }
+  if (next == cache->head) {
+    cache->head++;
+    if (cache->head >= kUserDictMissCacheSize) {
+      cache->head -= kUserDictMissCacheSize;
+    }
+  }
+  cache->tail = next;
+}
+
+void UserDict::reset_miss_cache() {
+  memset(miss_caches_, 0, sizeof(miss_caches_));
+}
+
+void UserDict::cache_init() {
+  reset_cache();
+  reset_miss_cache();
+}
+
+bool UserDict::cache_hit(UserDictSearchable *searchable,
+                         uint32 *offset, uint32 *length) {
+  bool hit = load_miss_cache(searchable);
+  if (hit) {
+    *offset = 0;
+    *length = 0;
+    return true;
+  }
+  hit = load_cache(searchable, offset, length);
+  if (hit) {
+    return true;
+  }
+  return false;
+}
+
+void UserDict::cache_push(UserDictCacheType type,
+                         UserDictSearchable *searchable,
+                         uint32 offset, uint32 length) {
+  switch (type) {
+    case USER_DICT_MISS_CACHE:
+      save_miss_cache(searchable);
+      break;
+    case USER_DICT_CACHE:
+      save_cache(searchable, offset, length);
+      break;
+    default:
+      break;
+  }
+}
+
+#endif
+
+void UserDict::defragment(void) {
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_BEGIN;
+#endif
+  if (is_valid_state() == false)
+    return;
+  // Fixup offsets_, set REMOVE flag to lemma's flag if needed
+  size_t first_freed = 0;
+  size_t first_inuse = 0;
+  while (first_freed < dict_info_.lemma_count) {
+    // Find first freed offset
+    while ((offsets_[first_freed] & kUserDictOffsetFlagRemove) == 0 &&
+            first_freed < dict_info_.lemma_count) {
+      first_freed++;
+    }
+    if (first_freed < dict_info_.lemma_count) {
+      // Save REMOVE flag to lemma flag
+      int off = offsets_[first_freed];
+      set_lemma_flag(off, kUserDictLemmaFlagRemove);
+    } else {
+      break;
+    }
+    // Find first inuse offse after first_freed
+    first_inuse = first_freed + 1;
+    while ((offsets_[first_inuse] & kUserDictOffsetFlagRemove) &&
+           (first_inuse < dict_info_.lemma_count)) {
+      // Save REMOVE flag to lemma flag
+      int off = offsets_[first_inuse];
+      set_lemma_flag(off, kUserDictLemmaFlagRemove);
+      first_inuse++;
+    }
+    if (first_inuse >= dict_info_.lemma_count) {
+      break;
+    }
+    // Swap offsets_
+    int tmp = offsets_[first_inuse];
+    offsets_[first_inuse] = offsets_[first_freed];
+    offsets_[first_freed] = tmp;
+    // Move scores_, no need to swap
+    tmp = scores_[first_inuse];
+    scores_[first_inuse] = scores_[first_freed];
+    scores_[first_freed] = tmp;
+    // Swap ids_
+    LemmaIdType tmpid = ids_[first_inuse];
+    ids_[first_inuse] = ids_[first_freed];
+    ids_[first_freed] = tmpid;
+    // Go on
+    first_freed++;
+  }
+#ifdef ___PREDICT_ENABLED___
+  // Fixup predicts_
+  first_freed = 0;
+  first_inuse = 0;
+  while (first_freed < dict_info_.lemma_count) {
+    // Find first freed offset
+    while ((predicts_[first_freed] & kUserDictOffsetFlagRemove) == 0 &&
+            first_freed < dict_info_.lemma_count) {
+      first_freed++;
+    }
+    if (first_freed >= dict_info_.lemma_count)
+      break;
+    // Find first inuse offse after first_freed
+    first_inuse = first_freed + 1;
+    while ((predicts_[first_inuse] & kUserDictOffsetFlagRemove)
+           && (first_inuse < dict_info_.lemma_count)) {
+      first_inuse++;
+    }
+    if (first_inuse >= dict_info_.lemma_count) {
+      break;
+    }
+    // Swap offsets_
+    int tmp = predicts_[first_inuse];
+    predicts_[first_inuse] = predicts_[first_freed];
+    predicts_[first_freed] = tmp;
+    // Go on
+    first_freed++;
+  }
+#endif
+  dict_info_.lemma_count = first_freed;
+  // Fixup lemmas_
+  size_t begin = 0;
+  size_t end = 0;
+  size_t dst = 0;
+  int total_size = dict_info_.lemma_size + lemma_size_left_;
+  int total_count = dict_info_.lemma_count + lemma_count_left_;
+  size_t real_size = total_size - lemma_size_left_;
+  while (dst < real_size) {
+    unsigned char flag = get_lemma_flag(dst);
+    unsigned char nchr = get_lemma_nchar(dst);
+    if ((flag & kUserDictLemmaFlagRemove) == 0) {
+      dst += nchr * 4 + 2;
+      continue;
+    }
+    break;
+  }
+  if (dst >= real_size)
+    return;
+
+  end = dst;
+  while (end < real_size) {
+    begin = end + get_lemma_nchar(end) * 4 + 2;
+ repeat:
+    // not used any more
+    if (begin >= real_size)
+      break;
+    unsigned char flag = get_lemma_flag(begin);
+    unsigned char nchr = get_lemma_nchar(begin);
+    if (flag & kUserDictLemmaFlagRemove) {
+      begin += nchr * 4 + 2;
+      goto repeat;
+    }
+    end = begin + nchr * 4 + 2;
+    while (end < real_size) {
+      unsigned char eflag = get_lemma_flag(end);
+      unsigned char enchr = get_lemma_nchar(end);
+      if ((eflag & kUserDictLemmaFlagRemove) == 0) {
+        end += enchr * 4 + 2;
+        continue;
+      }
+      break;
+    }
+    memmove(lemmas_ + dst, lemmas_ + begin, end - begin);
+    for (size_t j = 0; j < dict_info_.lemma_count; j++) {
+      if (offsets_[j] >= begin && offsets_[j] < end) {
+        offsets_[j] -= (begin - dst);
+        offsets_by_id_[ids_[j] - start_id_] = offsets_[j];
+      }
+#ifdef ___PREDICT_ENABLED___
+      if (predicts_[j] >= begin && predicts_[j] < end) {
+        predicts_[j] -= (begin - dst);
+      }
+#endif
+    }
+#ifdef ___SYNC_ENABLED___
+    for (size_t j = 0; j < dict_info_.sync_count; j++) {
+      if (syncs_[j] >= begin && syncs_[j] < end) {
+        syncs_[j] -= (begin - dst);
+      }
+    }
+#endif
+    dst += (end - begin);
+  }
+
+  dict_info_.free_count = 0;
+  dict_info_.free_size = 0;
+  dict_info_.lemma_size = dst;
+  lemma_size_left_ = total_size - dict_info_.lemma_size;
+  lemma_count_left_ = total_count - dict_info_.lemma_count;
+
+  // XXX Without following code,
+  // offsets_by_id_ is not reordered.
+  // That's to say, all removed lemmas' ids are not collected back.
+  // There may not be room for addition of new lemmas due to
+  // offsests_by_id_ reason, although lemma_size_left_ is fixed.
+  // By default, we do want defrag as fast as possible, because
+  // during defrag procedure, other peers can not write new lemmas
+  // to user dictionary file.
+  // XXX If write-back is invoked immediately after
+  // this defragment, no need to fix up following in-mem data.
+  for (uint32 i = 0; i < dict_info_.lemma_count; i++) {
+    ids_[i] = start_id_ + i;
+    offsets_by_id_[i] = offsets_[i];
+  }
+
+  state_ = USER_DICT_DEFRAGMENTED;
+
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_END;
+  LOGD_PERF("defragment");
+#endif
+}
+
+#ifdef ___SYNC_ENABLED___
+void UserDict::clear_sync_lemmas(unsigned int start, unsigned int end) {
+  if (is_valid_state() == false)
+    return;
+  if (end > dict_info_.sync_count)
+    end = dict_info_.sync_count;
+  memmove(syncs_ + start, syncs_ + end, (dict_info_.sync_count - end) << 2);
+  dict_info_.sync_count -= (end - start);
+  if (state_ < USER_DICT_SYNC_DIRTY)
+    state_ = USER_DICT_SYNC_DIRTY;
+}
+
+int UserDict::get_sync_count() {
+  if (is_valid_state() == false)
+    return 0;
+  return dict_info_.sync_count;
+}
+
+LemmaIdType UserDict::put_lemma_no_sync(char16 lemma_str[], uint16 splids[],
+                        uint16 lemma_len, uint16 count, uint64 lmt) {
+  int again = 0;
+ begin:
+  LemmaIdType id;
+  uint32 * syncs_bak = syncs_;
+  syncs_ = NULL;
+  id = _put_lemma(lemma_str, splids, lemma_len, count, lmt);
+  syncs_ = syncs_bak;
+  if (id == 0 && again == 0) {
+    if ((dict_info_.limit_lemma_count > 0 &&
+        dict_info_.lemma_count >= dict_info_.limit_lemma_count)
+        || (dict_info_.limit_lemma_size > 0 &&
+            dict_info_.lemma_size + (2 + (lemma_len << 2))
+            > dict_info_.limit_lemma_size)) {
+      // XXX Always reclaim and defrag in sync code path
+      //     sync thread is background thread and ok with heavy work
+      reclaim();
+      defragment();
+      flush_cache();
+      again = 1;
+      goto begin;
+    }
+  }
+  return id;
+}
+
+int UserDict::put_lemmas_no_sync_from_utf16le_string(char16 * lemmas, int len) {
+  int newly_added = 0;
+
+  SpellingParser * spl_parser = new SpellingParser();
+  if (!spl_parser) {
+    return 0;
+  }
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_BEGIN;
+#endif
+  char16 *ptr = lemmas;
+
+  // Extract pinyin,words,frequence,last_mod_time
+  char16 * p = ptr, * py16 = ptr;
+  char16 * hz16 = NULL;
+  int py16_len = 0;
+  uint16 * splid = NULL;
+  int splid_size = 0;
+  int splid_len = 0;
+  int hz16_len = 0;
+  char16 * fr16 = NULL;
+  int fr16_len = 0;
+
+  while (p - ptr < len) {
+    // Pinyin
+    py16 = p;
+    splid_len = 0;
+    while (*p != 0x2c && (p - ptr) < len) {
+      if (*p == 0x20)
+        splid_len++;
+      p++;
+    }
+    splid_len++;
+    if (p - ptr == len)
+      break;
+    py16_len = p - py16;
+    if (splid_size < splid_len) {
+      char16 * tid = (char16*)realloc(splid, splid_len * 2);
+      if (!tid)
+        break;
+      splid = tid;
+      splid_size = splid_len * 2;
+    }
+    bool is_pre;
+    int splidl = spl_parser->splstr16_to_idxs_f(
+        py16, py16_len, splid, NULL, splid_size, is_pre);
+    if (splidl != splid_len)
+      break;
+    // Phrase
+    hz16 = ++p;
+    while (*p != 0x2c && (p - ptr) < len) {
+      p++;
+    }
+    hz16_len = p - hz16;
+    if (hz16_len != splid_len)
+      break;
+    // Frequency
+    fr16 = ++p;
+    fr16_len = 0;
+    while (*p != 0x2c && (p - ptr) < len) {
+      p++;
+    }
+    fr16_len = p - fr16;
+    uint32 intf = (uint32)utf16le_atoll(fr16, fr16_len);
+    // Last modified time
+    fr16 = ++p;
+    fr16_len = 0;
+    while (*p != 0x3b && (p - ptr) < len) {
+      p++;
+    }
+    fr16_len = p - fr16;
+    uint64 last_mod = utf16le_atoll(fr16, fr16_len);
+
+    put_lemma_no_sync(hz16, splid, splid_len, intf, last_mod);
+    newly_added++;
+
+    p++;
+  }
+
+  if (splid)
+    free(splid);
+
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_END;
+  LOGD_PERF("put_lemmas_no_sync_from_utf16le_string");
+#endif
+  return newly_added;
+}
+
+int UserDict::get_sync_lemmas_in_utf16le_string_from_beginning(
+    char16 * str, int size, int * count) {
+  int len = 0;
+  *count = 0;
+
+  int left_len = size;
+
+  if (is_valid_state() == false)
+    return len;
+
+  SpellingTrie * spl_trie = &SpellingTrie::get_instance();
+  if (!spl_trie) {
+    return 0;
+  }
+
+  uint32 i;
+  for (i = 0; i < dict_info_.sync_count; i++) {
+    int offset = syncs_[i];
+    uint32 nchar = get_lemma_nchar(offset);
+    uint16 *spl = get_lemma_spell_ids(offset);
+    uint16 *wrd = get_lemma_word(offset);
+    int score = _get_lemma_score(wrd, spl, nchar);
+
+    static char score_temp[32], *pscore_temp = score_temp;
+    static char16 temp[256], *ptemp = temp;
+
+    pscore_temp = score_temp;
+    ptemp = temp;
+
+    uint32 j;
+    // Add pinyin
+    for (j = 0; j < nchar; j++) {
+      int ret_len = spl_trie->get_spelling_str16(
+          spl[j], ptemp, temp + sizeof(temp) - ptemp);
+      if (ret_len <= 0)
+        break;
+      ptemp += ret_len;
+      if (ptemp < temp + sizeof(temp) - 1) {
+        *(ptemp++) = ' ';
+      } else {
+        j = 0;
+        break;
+      }
+    }
+    if (j < nchar) {
+      continue;
+    }
+    ptemp--;
+    if (ptemp < temp + sizeof(temp) - 1) {
+      *(ptemp++) = ',';
+    } else {
+      continue;
+    }
+    // Add phrase
+    for (j = 0; j < nchar; j++) {
+      if (ptemp < temp + sizeof(temp) - 1) {
+        *(ptemp++) = wrd[j];
+      } else {
+        break;
+      }
+    }
+    if (j < nchar) {
+      continue;
+    }
+    if (ptemp < temp + sizeof(temp) - 1) {
+      *(ptemp++) = ',';
+    } else {
+      continue;
+    }
+    // Add frequency
+    uint32 intf = extract_score_freq(score);
+    int ret_len = utf16le_lltoa(intf, ptemp, temp + sizeof(temp) - ptemp);
+    if (ret_len <= 0)
+      continue;
+    ptemp += ret_len;
+    if (ptemp < temp + sizeof(temp) - 1) {
+      *(ptemp++) = ',';
+    } else {
+      continue;
+    }
+    // Add last modified time
+    uint64 last_mod = extract_score_lmt(score);
+    ret_len = utf16le_lltoa(last_mod, ptemp, temp + sizeof(temp) - ptemp);
+    if (ret_len <= 0)
+      continue;
+    ptemp += ret_len;
+    if (ptemp < temp + sizeof(temp) - 1) {
+      *(ptemp++) = ';';
+    } else {
+      continue;
+    }
+
+    // Write to string
+    int need_len = ptemp - temp;
+    if (need_len > left_len)
+      break;
+    memcpy(str + len, temp, need_len * 2);
+    left_len -= need_len;
+
+    len += need_len;
+    (*count)++;
+  }
+
+  if (len > 0) {
+    if (state_ < USER_DICT_SYNC_DIRTY)
+      state_ = USER_DICT_SYNC_DIRTY;
+  }
+  return len;
+}
+
+#endif
+
+bool UserDict::state(UserDictStat * stat) {
+  if (is_valid_state() == false)
+    return false;
+  if (!stat)
+    return false;
+  stat->version = version_;
+  stat->file_name = dict_file_;
+  stat->load_time.tv_sec = load_time_.tv_sec;
+  stat->load_time.tv_usec = load_time_.tv_usec;
+  pthread_mutex_lock(&g_mutex_);
+  stat->last_update.tv_sec = g_last_update_.tv_sec;
+  stat->last_update.tv_usec = g_last_update_.tv_usec;
+  pthread_mutex_unlock(&g_mutex_);
+  stat->disk_size = get_dict_file_size(&dict_info_);
+  stat->lemma_count = dict_info_.lemma_count;
+  stat->lemma_size = dict_info_.lemma_size;
+  stat->delete_count = dict_info_.free_count;
+  stat->delete_size = dict_info_.free_size;
+#ifdef ___SYNC_ENABLED___
+  stat->sync_count = dict_info_.sync_count;
+#endif
+  stat->limit_lemma_count = dict_info_.limit_lemma_count;
+  stat->limit_lemma_size = dict_info_.limit_lemma_size;
+  stat->reclaim_ratio = dict_info_.reclaim_ratio;
+  return true;
+}
+
+void UserDict::set_limit(uint32 max_lemma_count,
+                         uint32 max_lemma_size, uint32 reclaim_ratio) {
+  dict_info_.limit_lemma_count = max_lemma_count;
+  dict_info_.limit_lemma_size = max_lemma_size;
+  if (reclaim_ratio > 100)
+    reclaim_ratio = 100;
+  dict_info_.reclaim_ratio = reclaim_ratio;
+}
+
+void UserDict::reclaim() {
+  if (is_valid_state() == false)
+    return;
+
+  switch (dict_info_.reclaim_ratio) {
+    case 0:
+      return;
+    case 100:
+      // TODO: CLEAR to be implemented
+      assert(false);
+      return;
+    default:
+      break;
+  }
+
+  // XXX Reclaim is only based on count, not size
+  uint32 count = dict_info_.lemma_count;
+  int rc = count * dict_info_.reclaim_ratio / 100;
+
+  UserDictScoreOffsetPair * score_offset_pairs = NULL;
+  score_offset_pairs = (UserDictScoreOffsetPair *)malloc(
+      sizeof(UserDictScoreOffsetPair) * rc);
+  if (score_offset_pairs == NULL) {
+    return;
+  }
+
+  for (int i = 0; i < rc; i++) {
+    int s = scores_[i];
+    score_offset_pairs[i].score = s;
+    score_offset_pairs[i].offset_index = i;
+  }
+
+  for (int i = (rc + 1) / 2; i >= 0; i--)
+    shift_down(score_offset_pairs, i, rc);
+
+  for (uint32 i = rc; i < dict_info_.lemma_count; i++) {
+    int s = scores_[i];
+    if (s < score_offset_pairs[0].score) {
+      score_offset_pairs[0].score = s;
+      score_offset_pairs[0].offset_index = i;
+      shift_down(score_offset_pairs, 0, rc);
+    }
+  }
+
+  for (int i = 0; i < rc; i++) {
+    int off = score_offset_pairs[i].offset_index;
+    remove_lemma_by_offset_index(off);
+  }
+  if (rc > 0) {
+    if (state_ < USER_DICT_OFFSET_DIRTY)
+      state_ = USER_DICT_OFFSET_DIRTY;
+  }
+
+  free(score_offset_pairs);
+}
+
+inline void UserDict::swap(UserDictScoreOffsetPair * sop, int i, int j) {
+  int s = sop[i].score;
+  int p = sop[i].offset_index;
+  sop[i].score = sop[j].score;
+  sop[i].offset_index = sop[j].offset_index;
+  sop[j].score = s;
+  sop[j].offset_index = p;
+}
+
+void UserDict::shift_down(UserDictScoreOffsetPair * sop, int i, int n) {
+  int par = i;
+  while (par < n) {
+    int left = par * 2 + 1;
+    int right = left + 1;
+    if (left >= n && right >= n)
+      break;
+    if (right >= n) {
+      if (sop[left].score > sop[par].score) {
+        swap(sop, left, par);
+        par = left;
+        continue;
+      }
+    } else if (sop[left].score > sop[right].score &&
+               sop[left].score > sop[par].score) {
+      swap(sop, left, par);
+      par = left;
+      continue;
+    } else if (sop[right].score > sop[left].score &&
+               sop[right].score > sop[par].score) {
+      swap(sop, right, par);
+      par = right;
+      continue;
+    }
+    break;
+  }
+}
+
+LemmaIdType UserDict::put_lemma(char16 lemma_str[], uint16 splids[],
+                                uint16 lemma_len, uint16 count) {
+  return _put_lemma(lemma_str, splids, lemma_len, count, time(NULL));
+}
+
+LemmaIdType UserDict::_put_lemma(char16 lemma_str[], uint16 splids[],
+                                 uint16 lemma_len, uint16 count, uint64 lmt) {
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_BEGIN;
+#endif
+  if (is_valid_state() == false)
+    return 0;
+  int32 off = locate_in_offsets(lemma_str, splids, lemma_len);
+  if (off != -1) {
+    int delta_score = count - scores_[off];
+    dict_info_.total_nfreq += delta_score;
+    scores_[off] = build_score(lmt, count);
+    if (state_ < USER_DICT_SCORE_DIRTY)
+      state_ = USER_DICT_SCORE_DIRTY;
+#ifdef ___DEBUG_PERF___
+    DEBUG_PERF_END;
+    LOGD_PERF("_put_lemma(update)");
+#endif
+    return ids_[off];
+  } else {
+    if ((dict_info_.limit_lemma_count > 0 &&
+        dict_info_.lemma_count >= dict_info_.limit_lemma_count)
+        || (dict_info_.limit_lemma_size > 0 &&
+            dict_info_.lemma_size + (2 + (lemma_len << 2))
+            > dict_info_.limit_lemma_size)) {
+      // XXX Don't defragment here, it's too time-consuming.
+      return 0;
+    }
+    int flushed = 0;
+    if (lemma_count_left_ == 0 ||
+        lemma_size_left_ < (size_t)(2 + (lemma_len << 2))) {
+
+      // XXX When there is no space for new lemma, we flush to disk
+      // flush_cache() may be called by upper user
+      // and better place shoule be found instead of here
+      flush_cache();
+      flushed = 1;
+      // Or simply return and do nothing
+      // return 0;
+    }
+#ifdef ___DEBUG_PERF___
+    DEBUG_PERF_END;
+    LOGD_PERF(flushed ? "_put_lemma(flush+add)" : "_put_lemma(add)");
+#endif
+    LemmaIdType id = append_a_lemma(lemma_str, splids, lemma_len, count, lmt);
+#ifdef ___SYNC_ENABLED___
+    if (syncs_ && id != 0) {
+      queue_lemma_for_sync(id);
+    }
+#endif
+    return id;
+  }
+  return 0;
+}
+
+#ifdef ___SYNC_ENABLED___
+void UserDict::queue_lemma_for_sync(LemmaIdType id) {
+  if (dict_info_.sync_count < sync_count_size_) {
+    syncs_[dict_info_.sync_count++] = offsets_by_id_[id - start_id_];
+  } else {
+    uint32 * syncs = (uint32*)realloc(
+        syncs_, (sync_count_size_ + kUserDictPreAlloc) << 2);
+    if (syncs) {
+      sync_count_size_ += kUserDictPreAlloc;
+      syncs_ = syncs;
+      syncs_[dict_info_.sync_count++] = offsets_by_id_[id - start_id_];
+    }
+  }
+}
+#endif
+
+LemmaIdType UserDict::update_lemma(LemmaIdType lemma_id, int16 delta_count,
+                                   bool selected) {
+#ifdef ___DEBUG_PERF___
+  DEBUG_PERF_BEGIN;
+#endif
+  if (is_valid_state() == false)
+    return 0;
+  if (is_valid_lemma_id(lemma_id) == false)
+    return 0;
+  uint32 offset = offsets_by_id_[lemma_id - start_id_];
+  uint8 lemma_len = get_lemma_nchar(offset);
+  char16 * lemma_str = get_lemma_word(offset);
+  uint16 * splids = get_lemma_spell_ids(offset);
+
+  int32 off = locate_in_offsets(lemma_str, splids, lemma_len);
+  if (off != -1) {
+    int score = scores_[off];
+    int count = extract_score_freq(score);
+    uint64 lmt = extract_score_lmt(score);
+    if (count + delta_count > kUserDictMaxFrequency ||
+        count + delta_count < count) {
+      delta_count = kUserDictMaxFrequency - count;
+    }
+    count += delta_count;
+    dict_info_.total_nfreq += delta_count;
+    if (selected) {
+      lmt = time(NULL);
+    }
+    scores_[off] = build_score(lmt, count);
+    if (state_ < USER_DICT_SCORE_DIRTY)
+      state_ = USER_DICT_SCORE_DIRTY;
+#ifdef ___DEBUG_PERF___
+    DEBUG_PERF_END;
+    LOGD_PERF("update_lemma");
+#endif
+#ifdef ___SYNC_ENABLED___
+    queue_lemma_for_sync(ids_[off]);
+#endif
+    return ids_[off];
+  }
+  return 0;
+}
+
+size_t UserDict::get_total_lemma_count() {
+  return dict_info_.total_nfreq;
+}
+
+void UserDict::set_total_lemma_count_of_others(size_t count) {
+  total_other_nfreq_ = count;
+}
+
+LemmaIdType UserDict::append_a_lemma(char16 lemma_str[], uint16 splids[],
+                                   uint16 lemma_len, uint16 count, uint64 lmt) {
+  LemmaIdType id = get_max_lemma_id() + 1;
+  size_t offset = dict_info_.lemma_size;
+  if (offset > kUserDictOffsetMask)
+    return 0;
+
+  lemmas_[offset] = 0;
+  lemmas_[offset + 1] = (uint8)lemma_len;
+  for (size_t i = 0; i < lemma_len; i++) {
+    *((uint16*)&lemmas_[offset + 2 + (i << 1)]) = splids[i];
+    *((char16*)&lemmas_[offset + 2 + (lemma_len << 1) + (i << 1)])
+        = lemma_str[i];
+  }
+  uint32 off = dict_info_.lemma_count;
+  offsets_[off] = offset;
+  scores_[off] = build_score(lmt, count);
+  ids_[off] = id;
+#ifdef ___PREDICT_ENABLED___
+  predicts_[off] = offset;
+#endif
+
+  offsets_by_id_[id - start_id_] = offset;
+
+  dict_info_.lemma_count++;
+  dict_info_.lemma_size += (2 + (lemma_len << 2));
+  lemma_count_left_--;
+  lemma_size_left_ -= (2 + (lemma_len << 2));
+
+  // Sort
+
+  UserDictSearchable searchable;
+  prepare_locate(&searchable, splids, lemma_len);
+
+  size_t i = 0;
+  while (i < off) {
+    offset = offsets_[i];
+    uint32 nchar = get_lemma_nchar(offset);
+    uint16 * spl = get_lemma_spell_ids(offset);
+
+    if (0 <= fuzzy_compare_spell_id(spl, nchar, &searchable))
+      break;
+    i++;
+  }
+  if (i != off) {
+    uint32 temp = offsets_[off];
+    memmove(offsets_ + i + 1, offsets_ + i, (off - i) << 2);
+    offsets_[i] = temp;
+
+    temp = scores_[off];
+    memmove(scores_ + i + 1, scores_ + i, (off - i) << 2);
+    scores_[i] = temp;
+
+    temp = ids_[off];
+    memmove(ids_ + i + 1, ids_ + i, (off - i) << 2);
+    ids_[i] = temp;
+  }
+
+#ifdef ___PREDICT_ENABLED___
+  uint32 j = 0;
+  uint16 * words_new = get_lemma_word(predicts_[off]);
+  j = locate_where_to_insert_in_predicts(words_new, lemma_len);
+  if (j != off) {
+    uint32 temp = predicts_[off];
+    memmove(predicts_ + j + 1, predicts_ + j, (off - j) << 2);
+    predicts_[j] = temp;
+  }
+#endif
+
+  if (state_ < USER_DICT_LEMMA_DIRTY)
+    state_ = USER_DICT_LEMMA_DIRTY;
+
+#ifdef ___CACHE_ENABLED___
+  cache_init();
+#endif
+
+  dict_info_.total_nfreq += count;
+  return id;
+}
+}
diff --git a/PinyinIME/jni/share/utf16char.cpp b/PinyinIME/jni/share/utf16char.cpp
new file mode 100644
index 0000000..fadb6cf
--- /dev/null
+++ b/PinyinIME/jni/share/utf16char.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ */
+
+#include <stdlib.h>
+#include "../include/utf16char.h"
+
+namespace ime_pinyin {
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  char16* utf16_strtok(char16 *utf16_str, size_t *token_size,
+                       char16 **utf16_str_next) {
+    if (NULL == utf16_str || NULL == token_size || NULL == utf16_str_next) {
+      return NULL;
+    }
+
+    // Skip the splitters
+    size_t pos = 0;
+    while ((char16)' ' == utf16_str[pos] || (char16)'\n' == utf16_str[pos]
+           || (char16)'\t' == utf16_str[pos])
+      pos++;
+
+    utf16_str += pos;
+    pos = 0;
+
+    while ((char16)'\0' != utf16_str[pos] && (char16)' ' != utf16_str[pos]
+           && (char16)'\n' != utf16_str[pos]
+           && (char16)'\t' != utf16_str[pos]) {
+      pos++;
+    }
+
+    char16 *ret_val = utf16_str;
+    if ((char16)'\0' == utf16_str[pos]) {
+      *utf16_str_next = NULL;
+      if (0 == pos)
+        return NULL;
+    } else {
+      *utf16_str_next = utf16_str + pos + 1;
+    }
+
+    utf16_str[pos] = (char16)'\0';
+    *token_size = pos;
+
+    return ret_val;
+  }
+
+  int utf16_atoi(const char16 *utf16_str) {
+    if (NULL == utf16_str)
+      return 0;
+
+    int value = 0;
+    int sign = 1;
+    size_t pos = 0;
+
+    if ((char16)'-' == utf16_str[pos]) {
+      sign = -1;
+      pos++;
+    }
+
+    while ((char16)'0' <=  utf16_str[pos] &&
+           (char16)'9' >= utf16_str[pos]) {
+      value = value * 10 + static_cast<int>(utf16_str[pos] - (char16)'0');
+      pos++;
+    }
+
+    return value*sign;
+  }
+
+  float utf16_atof(const char16 *utf16_str) {
+    // A temporary implemetation.
+    char char8[256];
+    if (utf16_strlen(utf16_str) >= 256) return 0;
+
+    utf16_strcpy_tochar(char8, utf16_str);
+    return atof(char8);
+  }
+
+  size_t utf16_strlen(const char16 *utf16_str) {
+    if (NULL == utf16_str)
+      return 0;
+
+    size_t size = 0;
+    while ((char16)'\0' != utf16_str[size])
+      size++;
+    return size;
+  }
+
+  int utf16_strcmp(const char16* str1, const char16* str2) {
+    size_t pos = 0;
+    while (str1[pos] == str2[pos] && (char16)'\0' != str1[pos])
+      pos++;
+
+    return static_cast<int>(str1[pos]) - static_cast<int>(str2[pos]);
+  }
+
+  int utf16_strncmp(const char16 *str1, const char16 *str2, size_t size) {
+    size_t pos = 0;
+    while (pos < size && str1[pos] == str2[pos] && (char16)'\0' != str1[pos])
+      pos++;
+
+    if (pos == size)
+      return 0;
+
+    return static_cast<int>(str1[pos]) - static_cast<int>(str2[pos]);
+  }
+
+  // we do not consider overlapping
+  char16* utf16_strcpy(char16 *dst, const char16 *src) {
+    if (NULL == src || NULL == dst)
+      return NULL;
+
+    char16* cp = dst;
+
+    while ((char16)'\0' != *src) {
+      *cp = *src;
+      cp++;
+      src++;
+    }
+
+    *cp = *src;
+
+    return dst;
+  }
+
+  char16* utf16_strncpy(char16 *dst, const char16 *src, size_t size) {
+    if (NULL == src || NULL == dst || 0 == size)
+      return NULL;
+
+    if (src == dst)
+      return dst;
+
+    char16* cp = dst;
+
+    if (dst < src || (dst > src && dst >= src + size)) {
+      while (size-- && (*cp++ = *src++))
+        ;
+    } else {
+      cp += size - 1;
+      src += size - 1;
+      while (size-- && (*cp-- == *src--))
+        ;
+    }
+    return dst;
+  }
+
+  // We do not handle complicated cases like overlapping, because in this
+  // codebase, it is not necessary.
+  char* utf16_strcpy_tochar(char *dst, const char16 *src) {
+    if (NULL == src || NULL == dst)
+      return NULL;
+
+    char* cp = dst;
+
+    while ((char16)'\0' != *src) {
+      *cp = static_cast<char>(*src);
+      cp++;
+      src++;
+    }
+    *cp = *src;
+
+    return dst;
+  }
+
+#ifdef __cplusplus
+}
+#endif
+}  // namespace ime_pinyin
diff --git a/PinyinIME/jni/share/utf16reader.cpp b/PinyinIME/jni/share/utf16reader.cpp
new file mode 100644
index 0000000..d8e5de5
--- /dev/null
+++ b/PinyinIME/jni/share/utf16reader.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+
+#include "../include/utf16reader.h"
+
+namespace ime_pinyin {
+
+#define MIN_BUF_LEN 128
+#define MAX_BUF_LEN 65535
+
+Utf16Reader::Utf16Reader() {
+  fp_ = NULL;
+  buffer_ = NULL;
+  buffer_total_len_ = 0;
+  buffer_next_pos_ = 0;
+  buffer_valid_len_ = 0;
+}
+
+Utf16Reader::~Utf16Reader() {
+  if (NULL != fp_)
+    fclose(fp_);
+
+  if (NULL != buffer_)
+    delete [] buffer_;
+}
+
+
+bool Utf16Reader::open(const char* filename, size_t buffer_len) {
+  if (filename == NULL)
+    return false;
+
+  if (buffer_len < MIN_BUF_LEN)
+    buffer_len = MIN_BUF_LEN;
+  else if (buffer_len > MAX_BUF_LEN)
+    buffer_len = MAX_BUF_LEN;
+
+  buffer_total_len_ = buffer_len;
+
+  if (NULL != buffer_)
+    delete [] buffer_;
+  buffer_ = new char16[buffer_total_len_];
+  if (NULL == buffer_)
+    return false;
+
+  if ((fp_ = fopen(filename, "rb")) == NULL)
+    return false;
+
+  // the UTF16 file header, skip
+  char16 header;
+  if (fread(&header, sizeof(header), 1, fp_) != 1 || header != 0xfeff) {
+    fclose(fp_);
+    fp_ = NULL;
+    return false;
+  }
+
+  return true;
+}
+
+char16* Utf16Reader::readline(char16* read_buf, size_t max_len) {
+  if (NULL == fp_ || NULL == read_buf || 0 == max_len)
+    return NULL;
+
+  size_t ret_len = 0;
+
+  do {
+    if (buffer_valid_len_ == 0) {
+      buffer_next_pos_ = 0;
+      buffer_valid_len_ = fread(buffer_, sizeof(char16),
+                                buffer_total_len_, fp_);
+      if (buffer_valid_len_ == 0) {
+        if (0 == ret_len)
+          return NULL;
+        read_buf[ret_len] = (char16)'\0';
+        return read_buf;
+      }
+    }
+
+    for (size_t i = 0; i < buffer_valid_len_; i++) {
+      if (i == max_len - 1 ||
+          buffer_[buffer_next_pos_ + i] == (char16)'\n') {
+        if (ret_len + i > 0 && read_buf[ret_len + i - 1] == (char16)'\r') {
+          read_buf[ret_len + i - 1] = (char16)'\0';
+        } else {
+          read_buf[ret_len + i] = (char16)'\0';
+        }
+
+        i++;
+        buffer_next_pos_ += i;
+        buffer_valid_len_ -= i;
+        if (buffer_next_pos_ == buffer_total_len_) {
+          buffer_next_pos_ = 0;
+          buffer_valid_len_ = 0;
+        }
+        return read_buf;
+      } else {
+        read_buf[ret_len + i] = buffer_[buffer_next_pos_ + i];
+      }
+    }
+
+    ret_len += buffer_valid_len_;
+    buffer_valid_len_ = 0;
+  } while (true);
+
+  // Never reach here
+  return NULL;
+}
+
+bool Utf16Reader::close() {
+  if (NULL != fp_)
+    fclose(fp_);
+  fp_ = NULL;
+
+  if (NULL != buffer_)
+    delete [] buffer_;
+  buffer_ = NULL;
+  return true;
+}
+}  // namespace ime_pinyin
diff --git a/PinyinIME/lib/Android.mk b/PinyinIME/lib/Android.mk
new file mode 100644
index 0000000..f41193e
--- /dev/null
+++ b/PinyinIME/lib/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+         $(call all-subdir-java-files) \
+         com/android/inputmethod/pinyin/IPinyinDecoderService.aidl
+
+LOCAL_MODULE := com.android.inputmethod.pinyin.lib
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/PinyinIME/lib/com/android/inputmethod/pinyin/IPinyinDecoderService.aidl b/PinyinIME/lib/com/android/inputmethod/pinyin/IPinyinDecoderService.aidl
new file mode 100644
index 0000000..59dd6cc
--- /dev/null
+++ b/PinyinIME/lib/com/android/inputmethod/pinyin/IPinyinDecoderService.aidl
@@ -0,0 +1,51 @@
+/* //com/andriod/inputmethod/pinyin/IPinyinDecoderService.aidl
+ * 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.
+ */
+
+
+package com.android.inputmethod.pinyin;
+
+interface IPinyinDecoderService {
+    int getInt();
+    void setMaxLens(int maxSpsLen, int maxHzsLen);
+    int imSearch(in byte[] pyBuf, int pyLen);
+    int imDelSearch(int pos, boolean is_pos_in_splid, boolean clear_fixed_this_step);
+    void imResetSearch();
+    int imAddLetter(byte ch);
+    String imGetPyStr(boolean decoded);
+    int imGetPyStrLen(boolean decoded);
+    int[] imGetSplStart();
+    String imGetChoice(int choiceId);
+    String imGetChoices(int choicesNum);
+    List<String> imGetChoiceList(int choicesStart, int choicesNum, int sentFixedLen);
+    int imChoose(int choiceId);
+    int imCancelLastChoice();
+    int imGetFixedLen();
+    boolean imCancelInput();
+    void imFlushCache();
+    int imGetPredictsNum(in String fixedStr);
+    List<String> imGetPredictList(int predictsStart, int predictsNum);
+    String imGetPredictItem(int predictNo);
+
+    String syncUserDict(in String tomerge);
+    boolean syncBegin();
+    void syncFinish();
+    int syncPutLemmas(in String tomerge);
+    String syncGetLemmas();
+    int syncGetLastCount();
+    int syncGetTotalCount();
+    void syncClearLastGot();
+    int imSyncGetCapacity();
+}
diff --git a/PinyinIME/res/drawable/app_icon.png b/PinyinIME/res/drawable/app_icon.png
new file mode 100644
index 0000000..0c6eff3
--- /dev/null
+++ b/PinyinIME/res/drawable/app_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/arrow_bg.xml b/PinyinIME/res/drawable/arrow_bg.xml
new file mode 100644
index 0000000..9125232
--- /dev/null
+++ b/PinyinIME/res/drawable/arrow_bg.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<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/candidate_hl_bg" />
+</selector>
diff --git a/PinyinIME/res/drawable/arrow_left.png b/PinyinIME/res/drawable/arrow_left.png
new file mode 100644
index 0000000..75b8daa
--- /dev/null
+++ b/PinyinIME/res/drawable/arrow_left.png
Binary files differ
diff --git a/PinyinIME/res/drawable/arrow_right.png b/PinyinIME/res/drawable/arrow_right.png
new file mode 100644
index 0000000..e4e6cd7
--- /dev/null
+++ b/PinyinIME/res/drawable/arrow_right.png
Binary files differ
diff --git a/PinyinIME/res/drawable/candidate_balloon_bg.9.png b/PinyinIME/res/drawable/candidate_balloon_bg.9.png
new file mode 100644
index 0000000..ddc26f6
--- /dev/null
+++ b/PinyinIME/res/drawable/candidate_balloon_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/candidate_hl_bg.9.png b/PinyinIME/res/drawable/candidate_hl_bg.9.png
new file mode 100644
index 0000000..83d40a0
--- /dev/null
+++ b/PinyinIME/res/drawable/candidate_hl_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/candidates_area_bg.9.png b/PinyinIME/res/drawable/candidates_area_bg.9.png
new file mode 100644
index 0000000..3ff63f4
--- /dev/null
+++ b/PinyinIME/res/drawable/candidates_area_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/candidates_vertical_line.png b/PinyinIME/res/drawable/candidates_vertical_line.png
new file mode 100644
index 0000000..1edd335
--- /dev/null
+++ b/PinyinIME/res/drawable/candidates_vertical_line.png
Binary files differ
diff --git a/PinyinIME/res/drawable/cands_container_bg.9.png b/PinyinIME/res/drawable/cands_container_bg.9.png
new file mode 100644
index 0000000..837ba9b
--- /dev/null
+++ b/PinyinIME/res/drawable/cands_container_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/comma_full_icon.png b/PinyinIME/res/drawable/comma_full_icon.png
new file mode 100644
index 0000000..142fe1e
--- /dev/null
+++ b/PinyinIME/res/drawable/comma_full_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/comma_full_popup_icon.png b/PinyinIME/res/drawable/comma_full_popup_icon.png
new file mode 100644
index 0000000..f63a5b5
--- /dev/null
+++ b/PinyinIME/res/drawable/comma_full_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/composing_area_bg.9.png b/PinyinIME/res/drawable/composing_area_bg.9.png
new file mode 100644
index 0000000..31f6580
--- /dev/null
+++ b/PinyinIME/res/drawable/composing_area_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/composing_area_cursor.png b/PinyinIME/res/drawable/composing_area_cursor.png
new file mode 100644
index 0000000..ea3bbca
--- /dev/null
+++ b/PinyinIME/res/drawable/composing_area_cursor.png
Binary files differ
diff --git a/PinyinIME/res/drawable/composing_hl_bg.9.png b/PinyinIME/res/drawable/composing_hl_bg.9.png
new file mode 100644
index 0000000..b84719e
--- /dev/null
+++ b/PinyinIME/res/drawable/composing_hl_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/delete_icon.png b/PinyinIME/res/drawable/delete_icon.png
new file mode 100644
index 0000000..f1f7c58
--- /dev/null
+++ b/PinyinIME/res/drawable/delete_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/delete_popup_icon.png b/PinyinIME/res/drawable/delete_popup_icon.png
new file mode 100644
index 0000000..3c90839
--- /dev/null
+++ b/PinyinIME/res/drawable/delete_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/dun_icon.png b/PinyinIME/res/drawable/dun_icon.png
new file mode 100644
index 0000000..eaa6aa1
--- /dev/null
+++ b/PinyinIME/res/drawable/dun_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/dun_popup_icon.png b/PinyinIME/res/drawable/dun_popup_icon.png
new file mode 100644
index 0000000..5b7eb6a
--- /dev/null
+++ b/PinyinIME/res/drawable/dun_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_00.png b/PinyinIME/res/drawable/emotion_icon_00.png
new file mode 100644
index 0000000..33f2945
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_00.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_00_popup.png b/PinyinIME/res/drawable/emotion_icon_00_popup.png
new file mode 100644
index 0000000..1aaebdd
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_00_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_01.png b/PinyinIME/res/drawable/emotion_icon_01.png
new file mode 100644
index 0000000..9165e2a
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_01.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_01_popup.png b/PinyinIME/res/drawable/emotion_icon_01_popup.png
new file mode 100644
index 0000000..baeebdc
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_01_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_02.png b/PinyinIME/res/drawable/emotion_icon_02.png
new file mode 100644
index 0000000..e967603
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_02.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_02_popup.png b/PinyinIME/res/drawable/emotion_icon_02_popup.png
new file mode 100644
index 0000000..3a27346
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_02_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_03.png b/PinyinIME/res/drawable/emotion_icon_03.png
new file mode 100644
index 0000000..3fc2585
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_03.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_03_popup.png b/PinyinIME/res/drawable/emotion_icon_03_popup.png
new file mode 100644
index 0000000..2567632
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_03_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_04.png b/PinyinIME/res/drawable/emotion_icon_04.png
new file mode 100644
index 0000000..0e2d459
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_04.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_04_popup.png b/PinyinIME/res/drawable/emotion_icon_04_popup.png
new file mode 100644
index 0000000..61f2d21
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_04_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_05.png b/PinyinIME/res/drawable/emotion_icon_05.png
new file mode 100644
index 0000000..29ac101
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_05.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_05_popup.png b/PinyinIME/res/drawable/emotion_icon_05_popup.png
new file mode 100644
index 0000000..48fd0bc
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_05_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_06.png b/PinyinIME/res/drawable/emotion_icon_06.png
new file mode 100644
index 0000000..02f47c6
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_06.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_06_popup.png b/PinyinIME/res/drawable/emotion_icon_06_popup.png
new file mode 100644
index 0000000..5508cd2
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_06_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_10.png b/PinyinIME/res/drawable/emotion_icon_10.png
new file mode 100644
index 0000000..d9a0582
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_10.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_10_popup.png b/PinyinIME/res/drawable/emotion_icon_10_popup.png
new file mode 100644
index 0000000..32dd180
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_10_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_11.png b/PinyinIME/res/drawable/emotion_icon_11.png
new file mode 100644
index 0000000..d1ca0a8
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_11.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_11_popup.png b/PinyinIME/res/drawable/emotion_icon_11_popup.png
new file mode 100644
index 0000000..5e42d97
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_11_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_12.png b/PinyinIME/res/drawable/emotion_icon_12.png
new file mode 100644
index 0000000..ee88ef8
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_12.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_12_popup.png b/PinyinIME/res/drawable/emotion_icon_12_popup.png
new file mode 100644
index 0000000..195600d
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_12_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_13.png b/PinyinIME/res/drawable/emotion_icon_13.png
new file mode 100644
index 0000000..70b7334
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_13.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_13_popup.png b/PinyinIME/res/drawable/emotion_icon_13_popup.png
new file mode 100644
index 0000000..1df73ec
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_13_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_14.png b/PinyinIME/res/drawable/emotion_icon_14.png
new file mode 100644
index 0000000..852b7c1
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_14.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_14_popup.png b/PinyinIME/res/drawable/emotion_icon_14_popup.png
new file mode 100644
index 0000000..800a240
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_14_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_15.png b/PinyinIME/res/drawable/emotion_icon_15.png
new file mode 100644
index 0000000..cfbbe16
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_15.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_15_popup.png b/PinyinIME/res/drawable/emotion_icon_15_popup.png
new file mode 100644
index 0000000..7f175f5
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_15_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_16.png b/PinyinIME/res/drawable/emotion_icon_16.png
new file mode 100644
index 0000000..52368c0
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_16.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_16_popup.png b/PinyinIME/res/drawable/emotion_icon_16_popup.png
new file mode 100644
index 0000000..2a5b815
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_16_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_20.png b/PinyinIME/res/drawable/emotion_icon_20.png
new file mode 100644
index 0000000..2681132
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_20.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_20_popup.png b/PinyinIME/res/drawable/emotion_icon_20_popup.png
new file mode 100644
index 0000000..8887619
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_20_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_21.png b/PinyinIME/res/drawable/emotion_icon_21.png
new file mode 100644
index 0000000..ed425d8
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_21.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_21_popup.png b/PinyinIME/res/drawable/emotion_icon_21_popup.png
new file mode 100644
index 0000000..48afe8a
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_21_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_22.png b/PinyinIME/res/drawable/emotion_icon_22.png
new file mode 100644
index 0000000..88d37ec
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_22.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_22_popup.png b/PinyinIME/res/drawable/emotion_icon_22_popup.png
new file mode 100644
index 0000000..cb08d1b
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_22_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_23.png b/PinyinIME/res/drawable/emotion_icon_23.png
new file mode 100644
index 0000000..75f592b
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_23.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_23_popup.png b/PinyinIME/res/drawable/emotion_icon_23_popup.png
new file mode 100644
index 0000000..e3ecfc3
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_23_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_24.png b/PinyinIME/res/drawable/emotion_icon_24.png
new file mode 100644
index 0000000..fdadf91
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_24.png
Binary files differ
diff --git a/PinyinIME/res/drawable/emotion_icon_24_popup.png b/PinyinIME/res/drawable/emotion_icon_24_popup.png
new file mode 100644
index 0000000..1766a5d
--- /dev/null
+++ b/PinyinIME/res/drawable/emotion_icon_24_popup.png
Binary files differ
diff --git a/PinyinIME/res/drawable/enter_icon.png b/PinyinIME/res/drawable/enter_icon.png
new file mode 100644
index 0000000..17f2574
--- /dev/null
+++ b/PinyinIME/res/drawable/enter_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/enter_popup_icon.png b/PinyinIME/res/drawable/enter_popup_icon.png
new file mode 100644
index 0000000..03d9c9b
--- /dev/null
+++ b/PinyinIME/res/drawable/enter_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/ime_en.png b/PinyinIME/res/drawable/ime_en.png
new file mode 100644
index 0000000..652d7d7
--- /dev/null
+++ b/PinyinIME/res/drawable/ime_en.png
Binary files differ
diff --git a/PinyinIME/res/drawable/ime_pinyin.png b/PinyinIME/res/drawable/ime_pinyin.png
new file mode 100644
index 0000000..b398d89
--- /dev/null
+++ b/PinyinIME/res/drawable/ime_pinyin.png
Binary files differ
diff --git a/PinyinIME/res/drawable/key_balloon_bg.9.png b/PinyinIME/res/drawable/key_balloon_bg.9.png
new file mode 100644
index 0000000..1789b34
--- /dev/null
+++ b/PinyinIME/res/drawable/key_balloon_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/light_key_bg.9.png b/PinyinIME/res/drawable/light_key_bg.9.png
new file mode 100644
index 0000000..bda9b83
--- /dev/null
+++ b/PinyinIME/res/drawable/light_key_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/light_key_hl_bg.9.png b/PinyinIME/res/drawable/light_key_hl_bg.9.png
new file mode 100644
index 0000000..bdcf06e
--- /dev/null
+++ b/PinyinIME/res/drawable/light_key_hl_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/light_key_up_bg.9.png b/PinyinIME/res/drawable/light_key_up_bg.9.png
new file mode 100644
index 0000000..0c16ed5
--- /dev/null
+++ b/PinyinIME/res/drawable/light_key_up_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/light_key_up_hl_bg.9.png b/PinyinIME/res/drawable/light_key_up_hl_bg.9.png
new file mode 100644
index 0000000..79621a9
--- /dev/null
+++ b/PinyinIME/res/drawable/light_key_up_hl_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/miniskb_bg.9.png b/PinyinIME/res/drawable/miniskb_bg.9.png
new file mode 100644
index 0000000..74fddc8
--- /dev/null
+++ b/PinyinIME/res/drawable/miniskb_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/normal_key_bg.9.png b/PinyinIME/res/drawable/normal_key_bg.9.png
new file mode 100644
index 0000000..7ba18dd
--- /dev/null
+++ b/PinyinIME/res/drawable/normal_key_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/normal_key_hl_bg.9.png b/PinyinIME/res/drawable/normal_key_hl_bg.9.png
new file mode 100644
index 0000000..39b9314
--- /dev/null
+++ b/PinyinIME/res/drawable/normal_key_hl_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num0.png b/PinyinIME/res/drawable/num0.png
new file mode 100644
index 0000000..e7007c8
--- /dev/null
+++ b/PinyinIME/res/drawable/num0.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num1.png b/PinyinIME/res/drawable/num1.png
new file mode 100644
index 0000000..aaac11b
--- /dev/null
+++ b/PinyinIME/res/drawable/num1.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num2.png b/PinyinIME/res/drawable/num2.png
new file mode 100644
index 0000000..4372eb8
--- /dev/null
+++ b/PinyinIME/res/drawable/num2.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num3.png b/PinyinIME/res/drawable/num3.png
new file mode 100644
index 0000000..6f54c85
--- /dev/null
+++ b/PinyinIME/res/drawable/num3.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num4.png b/PinyinIME/res/drawable/num4.png
new file mode 100644
index 0000000..3e50bb9
--- /dev/null
+++ b/PinyinIME/res/drawable/num4.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num5.png b/PinyinIME/res/drawable/num5.png
new file mode 100644
index 0000000..c39ef44
--- /dev/null
+++ b/PinyinIME/res/drawable/num5.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num6.png b/PinyinIME/res/drawable/num6.png
new file mode 100644
index 0000000..ea88ceb
--- /dev/null
+++ b/PinyinIME/res/drawable/num6.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num7.png b/PinyinIME/res/drawable/num7.png
new file mode 100644
index 0000000..4d75583
--- /dev/null
+++ b/PinyinIME/res/drawable/num7.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num8.png b/PinyinIME/res/drawable/num8.png
new file mode 100644
index 0000000..1a8ff94
--- /dev/null
+++ b/PinyinIME/res/drawable/num8.png
Binary files differ
diff --git a/PinyinIME/res/drawable/num9.png b/PinyinIME/res/drawable/num9.png
new file mode 100644
index 0000000..8b344c0
--- /dev/null
+++ b/PinyinIME/res/drawable/num9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/numalt.png b/PinyinIME/res/drawable/numalt.png
new file mode 100644
index 0000000..32a2cf3
--- /dev/null
+++ b/PinyinIME/res/drawable/numalt.png
Binary files differ
diff --git a/PinyinIME/res/drawable/numpound.png b/PinyinIME/res/drawable/numpound.png
new file mode 100644
index 0000000..b2419d9
--- /dev/null
+++ b/PinyinIME/res/drawable/numpound.png
Binary files differ
diff --git a/PinyinIME/res/drawable/numstar.png b/PinyinIME/res/drawable/numstar.png
new file mode 100644
index 0000000..cb66f96
--- /dev/null
+++ b/PinyinIME/res/drawable/numstar.png
Binary files differ
diff --git a/PinyinIME/res/drawable/period_full_icon.png b/PinyinIME/res/drawable/period_full_icon.png
new file mode 100644
index 0000000..1d7c6c7
--- /dev/null
+++ b/PinyinIME/res/drawable/period_full_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/period_full_popup_icon.png b/PinyinIME/res/drawable/period_full_popup_icon.png
new file mode 100644
index 0000000..07ee288
--- /dev/null
+++ b/PinyinIME/res/drawable/period_full_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/period_icon.png b/PinyinIME/res/drawable/period_icon.png
new file mode 100644
index 0000000..6a8bd86
--- /dev/null
+++ b/PinyinIME/res/drawable/period_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/period_popup_icon.png b/PinyinIME/res/drawable/period_popup_icon.png
new file mode 100644
index 0000000..3e18e03
--- /dev/null
+++ b/PinyinIME/res/drawable/period_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/search_icon.png b/PinyinIME/res/drawable/search_icon.png
new file mode 100644
index 0000000..127755d
--- /dev/null
+++ b/PinyinIME/res/drawable/search_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/search_popup_icon.png b/PinyinIME/res/drawable/search_popup_icon.png
new file mode 100644
index 0000000..f4af341
--- /dev/null
+++ b/PinyinIME/res/drawable/search_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/shift_off_icon.png b/PinyinIME/res/drawable/shift_off_icon.png
new file mode 100644
index 0000000..0566e5a
--- /dev/null
+++ b/PinyinIME/res/drawable/shift_off_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/shift_off_popup_icon.png b/PinyinIME/res/drawable/shift_off_popup_icon.png
new file mode 100644
index 0000000..97f4661
--- /dev/null
+++ b/PinyinIME/res/drawable/shift_off_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/shift_on_icon.png b/PinyinIME/res/drawable/shift_on_icon.png
new file mode 100644
index 0000000..ccaf05d
--- /dev/null
+++ b/PinyinIME/res/drawable/shift_on_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/shift_on_popup_icon.png b/PinyinIME/res/drawable/shift_on_popup_icon.png
new file mode 100644
index 0000000..7194b30
--- /dev/null
+++ b/PinyinIME/res/drawable/shift_on_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/skb_bg.png b/PinyinIME/res/drawable/skb_bg.png
new file mode 100644
index 0000000..f582462
--- /dev/null
+++ b/PinyinIME/res/drawable/skb_bg.png
Binary files differ
diff --git a/PinyinIME/res/drawable/skb_container_bg.9.png b/PinyinIME/res/drawable/skb_container_bg.9.png
new file mode 100644
index 0000000..d6d7537
--- /dev/null
+++ b/PinyinIME/res/drawable/skb_container_bg.9.png
Binary files differ
diff --git a/PinyinIME/res/drawable/smiley_icon.png b/PinyinIME/res/drawable/smiley_icon.png
new file mode 100644
index 0000000..daa2dc1
--- /dev/null
+++ b/PinyinIME/res/drawable/smiley_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/smiley_popup_icon.png b/PinyinIME/res/drawable/smiley_popup_icon.png
new file mode 100644
index 0000000..5d50370
--- /dev/null
+++ b/PinyinIME/res/drawable/smiley_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/space_icon.png b/PinyinIME/res/drawable/space_icon.png
new file mode 100644
index 0000000..4e6273b
--- /dev/null
+++ b/PinyinIME/res/drawable/space_icon.png
Binary files differ
diff --git a/PinyinIME/res/drawable/space_popup_icon.png b/PinyinIME/res/drawable/space_popup_icon.png
new file mode 100644
index 0000000..739db68
--- /dev/null
+++ b/PinyinIME/res/drawable/space_popup_icon.png
Binary files differ
diff --git a/PinyinIME/res/layout/candidates_container.xml b/PinyinIME/res/layout/candidates_container.xml
new file mode 100644
index 0000000..eda7e28
--- /dev/null
+++ b/PinyinIME/res/layout/candidates_container.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+<com.android.inputmethod.pinyin.CandidatesContainer xmlns:android="http://schemas.android.com/apk/res/android"
+  android:id="@+id/candidates_container"
+  android:background="@drawable/cands_container_bg"
+  android:orientation="horizontal"
+  android:layout_width="0dip"
+  android:layout_height="0dip">
+
+  <ImageButton android:id="@+id/arrow_left_btn"
+    android:background="@drawable/arrow_bg"
+    android:src="@drawable/arrow_left"
+    android:layout_height="fill_parent"
+    android:layout_width="30dip"
+    android:clickable="true"
+    android:layout_alignParentLeft="true"/>
+
+  <ImageButton android:id="@+id/arrow_right_btn"
+    android:background="@drawable/arrow_bg"
+    android:src="@drawable/arrow_right"
+    android:layout_width="30dip"
+    android:layout_height="fill_parent"
+    android:clickable="true"
+    android:layout_alignParentRight="true"/>
+
+  <ViewFlipper android:id="@+id/candidate_flipper"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:layout_toLeftOf="@id/arrow_right_btn"
+    android:layout_toRightOf="@id/arrow_left_btn">
+    <view class="com.android.inputmethod.pinyin.CandidateView"
+        android:id="@+id/candidate_view1"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" />
+    <view class="com.android.inputmethod.pinyin.CandidateView"
+        android:id="@+id/candidate_view2"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" />
+  </ViewFlipper>
+</com.android.inputmethod.pinyin.CandidatesContainer>
diff --git a/PinyinIME/res/layout/floating_container.xml b/PinyinIME/res/layout/floating_container.xml
new file mode 100644
index 0000000..ad9f3fd
--- /dev/null
+++ b/PinyinIME/res/layout/floating_container.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:id="@+id/floating_container"
+  android:orientation="vertical"
+  android:layout_width="wrap_content"
+  android:layout_height="wrap_content">
+
+  <com.android.inputmethod.pinyin.ComposingView
+    android:id="@+id/composing_view"
+    android:background="@drawable/composing_area_bg"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/PinyinIME/res/layout/skb_container.xml b/PinyinIME/res/layout/skb_container.xml
new file mode 100644
index 0000000..5a20aab
--- /dev/null
+++ b/PinyinIME/res/layout/skb_container.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+<com.android.inputmethod.pinyin.SkbContainer xmlns:android="http://schemas.android.com/apk/res/android"
+  android:orientation="vertical"
+  android:background="@drawable/skb_container_bg"
+  android:layout_width="wrap_content"
+  android:layout_height="wrap_content" >
+
+  <ViewFlipper
+    android:id="@+id/alpha_floatable"
+    android:layout_alignParentBottom="true"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" >
+
+   <com.android.inputmethod.pinyin.SoftKeyboardView
+     android:id="@+id/alpha_view1"
+     android:layout_width="wrap_content"
+     android:layout_height="wrap_content"/>
+   <com.android.inputmethod.pinyin.SoftKeyboardView
+     android:id="@+id/alpha_view2"
+     android:layout_width="wrap_content"
+     android:layout_height="wrap_content"/>
+  </ViewFlipper>
+</com.android.inputmethod.pinyin.SkbContainer>
diff --git a/PinyinIME/res/raw/dict_pinyin.dat b/PinyinIME/res/raw/dict_pinyin.dat
new file mode 100644
index 0000000..1be3f9c
--- /dev/null
+++ b/PinyinIME/res/raw/dict_pinyin.dat
Binary files differ
diff --git a/PinyinIME/res/values-land/dimens.xml b/PinyinIME/res/values-land/dimens.xml
new file mode 100644
index 0000000..3ee7751
--- /dev/null
+++ b/PinyinIME/res/values-land/dimens.xml
@@ -0,0 +1,19 @@
+<?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>
+    <dimen name="candidate_margin_left_right">10dip</dimen>
+    <dimen name="composing_height">16dip</dimen>
+</resources>
diff --git a/PinyinIME/res/values-port/dimens.xml b/PinyinIME/res/values-port/dimens.xml
new file mode 100644
index 0000000..47a3a53
--- /dev/null
+++ b/PinyinIME/res/values-port/dimens.xml
@@ -0,0 +1,19 @@
+<?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>
+    <dimen name="candidate_margin_left_right">3dip</dimen>
+    <dimen name="composing_height">18dip</dimen>
+</resources>
diff --git a/PinyinIME/res/values-zh/bools.xml b/PinyinIME/res/values-zh/bools.xml
new file mode 100644
index 0000000..c3291be
--- /dev/null
+++ b/PinyinIME/res/values-zh/bools.xml
@@ -0,0 +1,18 @@
+<?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>
+  <bool name="im_is_default">true</bool>
+</resources>
diff --git a/PinyinIME/res/values/colors.xml b/PinyinIME/res/values/colors.xml
new file mode 100644
index 0000000..e66a1ce
--- /dev/null
+++ b/PinyinIME/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?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>
+    <color name="label_color">#ffffffff</color>
+    <color name="label_color_hl0">#ffffffff</color>
+    <color name="balloon_color">#ff000000</color>
+    <color name="candidate_color">#ff000000</color>
+    <color name="active_candidate_color">#ff000000</color>
+    <color name="recommended_candidate_color">#ffe35900</color>
+    <color name="footnote_color">#ff343233</color>
+    <color name="composing_color">#ff000000</color>
+    <color name="composing_color_hl">#ffffffff</color>
+    <color name="composing_color_idle">#ff777777</color>
+</resources>
diff --git a/PinyinIME/res/values/dimens.xml b/PinyinIME/res/values/dimens.xml
new file mode 100644
index 0000000..0c8519e
--- /dev/null
+++ b/PinyinIME/res/values/dimens.xml
@@ -0,0 +1,18 @@
+<?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>
+  <dimen name="candview_background_padding">0dip</dimen>
+</resources>
diff --git a/PinyinIME/res/values/strings.xml b/PinyinIME/res/values/strings.xml
new file mode 100644
index 0000000..09d64db
--- /dev/null
+++ b/PinyinIME/res/values/strings.xml
@@ -0,0 +1,61 @@
+<?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:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="ime_name">谷歌拼音输入法</string>
+    <string name="ime_settings_activity_name">谷歌拼音输入法设置</string>
+    <!-- version = apk(java) + ime engine + sys dict + [hotlist (TBD)] -->
+    <string name="version">1.0.0</string>
+
+    <!-- Used by the toggle keys in soft keyboards.-->
+    <string name="toggle_cn">0</string>
+    <string name="toggle_cn_cand">1</string>
+    <string name="toggle_en_lower">2</string>
+    <string name="toggle_en_upper">3</string>
+    <string name="toggle_en_sym1">4</string>
+    <string name="toggle_en_sym2">5</string>
+    <string name="toggle_smiley">6</string>
+    <string name="toggle_enter_go">7</string>
+    <string name="toggle_enter_search">8</string>
+    <string name="toggle_enter_send">9</string>
+    <string name="toggle_enter_next">10</string>
+    <string name="toggle_enter_done">11</string>
+    <string name="toggle_row_cn">12</string>
+    <string name="toggle_row_en">13</string>
+    <string name="toggle_row_uri">14</string>
+    <string name="toggle_row_emailaddress">15</string>
+    <string name="toggle_phone_sym">3</string>
+
+    <!-- settings related -->
+    <string name="setting_sound_key">setting_sound_key</string>
+    <string name="setting_vibrate_key">setting_vibrate_key</string>
+    <string name="setting_prediction_key">setting_prediction_key</string>
+    <string name="setting_switch_key">setting_switch_key</string>
+    <string name="setting_advanced_key">setting_advanced_key</string>
+
+	<string name="setting">谷歌拼音输入法设置</string>
+    <string name="setting_sound_key_title">按键声音</string>
+    <string name="setting_vibrate_title">按键震动</string>
+    <string name="setting_prediction_title">联想输入</string>
+    <string name="setting_switch_title">中英文切换</string>
+    <string name="setting_switch_shift_space_title">Shift-space</string>
+
+    <string name="setting_enabled">开启</string>
+    <string name="setting_disabled">关闭</string>
+
+    <string name="setting_others">其它设置</string>
+    <string name="setting_others_summary">词典同步等</string>
+
+</resources>
diff --git a/PinyinIME/res/xml/method.xml b/PinyinIME/res/xml/method.xml
new file mode 100644
index 0000000..2ff16b8
--- /dev/null
+++ b/PinyinIME/res/xml/method.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<!-- The attributes in this XML file provide configuration information -->
+<!-- for the Search Manager. -->
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+        android:settingsActivity="com.android.inputmethod.pinyin.SettingsActivity"
+        android:isDefault="@bool/im_is_default"
+/>
diff --git a/PinyinIME/res/xml/settings.xml b/PinyinIME/res/xml/settings.xml
new file mode 100644
index 0000000..b46e621
--- /dev/null
+++ b/PinyinIME/res/xml/settings.xml
@@ -0,0 +1,55 @@
+<?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.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/setting">
+
+    <CheckBoxPreference
+      android:key="@string/setting_sound_key"
+      android:title="@string/setting_sound_key_title"
+      android:persistent="true"
+      android:summaryOn="@string/setting_enabled"
+      android:summaryOff="@string/setting_disabled"/>
+
+    <CheckBoxPreference
+      android:key="@string/setting_vibrate_key"
+      android:title="@string/setting_vibrate_title"
+      android:persistent="false"
+      android:summaryOn="@string/setting_enabled"
+      android:summaryOff="@string/setting_disabled"/>
+
+    <CheckBoxPreference 
+      android:key="@string/setting_prediction_key" 
+      android:title="@string/setting_prediction_title"
+      android:persistent="true"
+      android:summaryOn="@string/setting_enabled"
+      android:summaryOff="@string/setting_disabled"/>
+    
+    <!-- Remove following entry to unbundle Google functionality -->
+    <PreferenceScreen
+      android:title="@string/setting_others"
+      android:summary="@string/setting_others_summary"
+      android:key="@string/setting_advanced_key">
+      <intent android:action="com.android.inputmethod.pinyingoogleservice.SETTINGS" />
+    </PreferenceScreen>
+
+	<Preference
+      android:key="@string/setting_switch_key"
+      style="?android:preferenceInformationStyle"
+      android:title="@string/setting_switch_title"
+      android:persistent="false"
+      android:summary="@string/setting_switch_shift_space_title"/>
+     
+</PreferenceScreen>
diff --git a/PinyinIME/res/xml/skb_phone.xml b/PinyinIME/res/xml/skb_phone.xml
new file mode 100644
index 0000000..80a44fb
--- /dev/null
+++ b/PinyinIME/res/xml/skb_phone.xml
@@ -0,0 +1,75 @@
+<?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.
+-->
+<keyboard
+  skb_template="@xml/skb_template1"
+  width="25%p"
+  height="25%p"
+  key_type="0"
+  repeat="false"
+  balloon="false">
+
+  <row>
+    <key icon="@drawable/num1" code="8">
+      <toggle_state state_id="@string/toggle_phone_sym" label="("/>
+    </key>
+    <key icon="@drawable/num2" code="9">
+      <toggle_state state_id="@string/toggle_phone_sym" label="/"/>
+    </key>
+    <key icon="@drawable/num3" code="10">
+      <toggle_state state_id="@string/toggle_phone_sym" label=")"/>
+    </key>
+    <key label="-"/>
+  </row>
+
+  <row>
+    <key icon="@drawable/num4" code="11">
+      <toggle_state state_id="@string/toggle_phone_sym" label="N"/>
+    </key>
+    <key icon="@drawable/num5" code="12">
+      <toggle_state state_id="@string/toggle_phone_sym" label="暂 停" code="44"/>
+    </key>
+    <key icon="@drawable/num6" code="13">
+      <toggle_state state_id="@string/toggle_phone_sym" label=","/>
+    </key>
+    <key label="."/>
+  </row>
+
+  <row>
+    <key icon="@drawable/num7" code="14">
+      <toggle_state state_id="@string/toggle_phone_sym" label="*"
+        icon="@drawable/numstar"/>
+    </key>
+    <key icon="@drawable/num8" code="15">
+      <toggle_state state_id="@string/toggle_phone_sym" label="等 待" code="51"/>
+    </key>
+    <key icon="@drawable/num9" code="16">
+      <toggle_state state_id="@string/toggle_phone_sym" label=","
+        icon="@drawable/numpound"/>
+    </key>
+    <key code="67" key_type="1" repeat="true"/>
+  </row>
+
+  <row>
+    <key icon="@drawable/numalt" code="-4" repeat="true">
+      <toggle_state state_id="@string/toggle_phone_sym" label="123" code="-4"/>
+    </key>
+    <key icon="@drawable/num0" code="7">
+      <toggle_state state_id="@string/toggle_phone_sym" label="+"/>
+    </key>
+    <key code="62"/>
+    <key id="2"/>
+  </row>
+</keyboard>
diff --git a/PinyinIME/res/xml/skb_qwerty.xml b/PinyinIME/res/xml/skb_qwerty.xml
new file mode 100644
index 0000000..57b8838
--- /dev/null
+++ b/PinyinIME/res/xml/skb_qwerty.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.
+-->
+<keyboard
+  skb_template="@xml/skb_template1"
+  skb_cache_flag="true"
+  qwerty="true"
+  qwerty_uppercase="true"
+  width="10%p"
+  height="25%p"
+  key_type="0"
+  repeat="false"
+  balloon="true">
+
+  <row>
+    <keys splitter="|" labels="Q|W|E|R|T|Y|U|I|O|P"
+      codes="45|51|33|46|48|53|49|37|43|44"/>
+  </row>
+
+  <row start_pos_x="4.078%p" width="10.205%p">
+    <keys splitter="|" labels="A|S|D|F|G|H|J|K|L"
+      codes="29|47|32|34|35|36|38|39|40"/>
+  </row>
+
+  <row width="10.205%p">
+    <key label="," width="14.286%p" icon="@drawable/comma_full_icon"
+      icon_popup="@drawable/comma_full_popup_icon">
+      <toggle_state state_id="@string/toggle_smiley" code="-6"
+        icon="@drawable/smiley_icon" icon_popup="@drawable/smiley_popup_icon"
+        key_type="2"/>
+      <toggle_state state_id="@string/toggle_en_lower" code="-1"
+        icon="@drawable/shift_off_icon"
+        icon_popup="@drawable/shift_off_popup_icon" key_type="2"/>
+      <toggle_state state_id="@string/toggle_en_upper" code="-1"
+        icon="@drawable/shift_on_icon"
+        icon_popup="@drawable/shift_on_popup_icon" key_type="3"/>
+      <toggle_state state_id="@string/toggle_cn_cand" label="'"
+        key_type="1"/>
+    </key>
+    <keys splitter="|" labels="Z|X|C|V|B|N|M"
+      codes="54|52|31|50|30|42|41"/>
+    <key id="3"/>
+  </row>
+
+  <row width="20%p" row_id="@string/toggle_row_cn">
+    <key id="6"/>
+    <key id="4"/>
+    <key code="62" key_type="5" width="30.608%p"/>
+    <key label="。" width="14.696%p" icon="@drawable/period_full_icon"
+      icon_popup="@drawable/period_full_popup_icon"/>
+    <key id="1"/>
+  </row>
+
+  <row width="20%p" row_id="@string/toggle_row_en" start_pos_y="75%p">
+    <key id="6"/>
+    <key id="4"/>
+    <key code="62" key_type="5" width="30.608%p"/>
+    <key id="7"/>
+    <key id="1"/>
+  </row>
+
+  <row width="20%p" row_id="@string/toggle_row_uri" start_pos_y="75%p">
+    <key id="6"/>
+    <key id="4"/>
+    <key label="/" width="15.304%p"/>
+    <key code="62" key_type="5" width="15.304%p"/>
+    <key id="7"/>
+    <key id="1"/>
+  </row>
+
+  <row width="20%p" row_id="@string/toggle_row_emailaddress" start_pos_y="75%p">
+    <key id="6"/>
+    <key id="4"/>
+    <key label="\@" width="15.304%p"/>
+    <key code="62" key_type="5" width="15.304%p"/>
+    <key id="7"/>
+    <key id="1"/>
+  </row>
+</keyboard>
+
diff --git a/PinyinIME/res/xml/skb_smiley.xml b/PinyinIME/res/xml/skb_smiley.xml
new file mode 100644
index 0000000..ec48b12
--- /dev/null
+++ b/PinyinIME/res/xml/skb_smiley.xml
@@ -0,0 +1,85 @@
+<?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.
+-->
+<keyboard
+  skb_template="@xml/skb_template1"
+  skb_cache_flag="true"
+  skb_sticky_flag="false"
+  width="14.286%p"
+  height="25%p"
+  key_type="0"
+  repeat="false"
+  balloon="true">
+
+  <row>
+    <key label=":)" icon="@drawable/emotion_icon_00"
+      icon_popup="@drawable/emotion_icon_00_popup"/>
+    <key label=";)" icon="@drawable/emotion_icon_01"
+      icon_popup="@drawable/emotion_icon_01_popup"/>
+    <key label=":P" icon="@drawable/emotion_icon_02"
+      icon_popup="@drawable/emotion_icon_02_popup"/>
+    <key label=":D" icon="@drawable/emotion_icon_03"
+      icon_popup="@drawable/emotion_icon_03_popup"/>
+    <key label="^_^" icon="@drawable/emotion_icon_04"
+      icon_popup="@drawable/emotion_icon_04_popup"/>
+    <key label="^O^" icon="@drawable/emotion_icon_05"
+      icon_popup="@drawable/emotion_icon_05_popup"/>
+    <key label=":-*" icon="@drawable/emotion_icon_06"
+      icon_popup="@drawable/emotion_icon_06_popup"/>
+  </row>
+
+  <row>
+    <key label=":(" icon="@drawable/emotion_icon_10"
+      icon_popup="@drawable/emotion_icon_10_popup"/>
+    <key label=":'(" icon="@drawable/emotion_icon_11"
+      icon_popup="@drawable/emotion_icon_11_popup"/>
+    <key label=":S" icon="@drawable/emotion_icon_12"
+      icon_popup="@drawable/emotion_icon_12_popup"/>
+    <key label=":O" icon="@drawable/emotion_icon_13"
+      icon_popup="@drawable/emotion_icon_13_popup"/>
+    <key label="-_-" icon="@drawable/emotion_icon_14"
+      icon_popup="@drawable/emotion_icon_14_popup"/>
+    <key label="-_-z" icon="@drawable/emotion_icon_15"
+      icon_popup="@drawable/emotion_icon_15_popup"/>
+    <key label="-_-||" icon="@drawable/emotion_icon_16"
+      icon_popup="@drawable/emotion_icon_16_popup"/>
+  </row>
+
+  <row>
+    <key code="-6" key_type="3" icon="@drawable/smiley_icon"
+      icon_popup="@drawable/smiley_popup_icon"/>
+    <key label="¬_¬" icon="@drawable/emotion_icon_20"
+      icon_popup="@drawable/emotion_icon_20_popup"/>
+    <key label="\@_\@" icon="@drawable/emotion_icon_21"
+      icon_popup="@drawable/emotion_icon_21_popup"/>
+    <key label="&gt;_&lt;" icon="@drawable/emotion_icon_22"
+      icon_popup="@drawable/emotion_icon_22_popup"/>
+    <key label="T_T" icon="@drawable/emotion_icon_23"
+      icon_popup="@drawable/emotion_icon_23_popup"/>
+    <key label="囧" icon="@drawable/emotion_icon_24"
+      icon_popup="@drawable/emotion_icon_24_popup"/>
+    <key id="3"/>
+  </row>
+
+  <row width="14.696%p" key_type="1">
+    <key id="6"/>
+    <key code="-3" key_type="2" label="\?123"/>
+    <key code="62" key_type="5" width="30.608%p"/>
+    <key label="。" icon="@drawable/period_full_icon"
+      icon_popup="@drawable/period_full_popup_icon"/>
+    <key id="1"/>
+  </row>
+</keyboard>
+
diff --git a/PinyinIME/res/xml/skb_sym1.xml b/PinyinIME/res/xml/skb_sym1.xml
new file mode 100644
index 0000000..918fd33
--- /dev/null
+++ b/PinyinIME/res/xml/skb_sym1.xml
@@ -0,0 +1,88 @@
+<?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.
+-->
+<keyboard
+  skb_template="@xml/skb_template1"
+  skb_cache_flag="true"
+  width="10%p"
+  height="25%p"
+  key_type="0"
+  repeat="false"
+  balloon="true">
+
+  <row>
+    <keys splitter="|" labels="1|2|3|4|5|6|7|8|9|0"
+      codes="8|9|10|11|12|13|14|15|16|7"/>
+  </row>
+
+  <row>
+    <keys splitter="|" labels="\@|#"/>
+    <key label="¥">
+      <toggle_state state_id="@string/toggle_en_sym1" label="$"/>
+    </key>
+    <keys splitter="|" labels="%|&quot;|*"/>
+    <key label="—">
+      <toggle_state state_id="@string/toggle_en_sym1" label="-"/>
+    </key>
+    <key label="、" icon="@drawable/dun_icon"
+      icon_popup="@drawable/dun_popup_icon">
+      <toggle_state state_id="@string/toggle_en_sym1" label="/"/>
+    </key>
+    <keys splitter="|" labels="(|)"/>
+  </row>
+
+  <row width="10.205%p">
+    <key code="-5" width="14.286%p" key_type="2" label="ALT"/>
+    <key label="!">
+      <toggle_state state_id="@string/toggle_en_sym1" label="!"/>
+    </key>
+    <key label="“">
+      <toggle_state state_id="@string/toggle_en_sym1" label="&quot;"/>
+    </key>
+    <key label="”">
+      <toggle_state state_id="@string/toggle_en_sym1" label="'"/>
+    </key>
+    <key label=":">
+      <toggle_state state_id="@string/toggle_en_sym1" label=":"/>
+    </key>
+    <key label=";">
+      <toggle_state state_id="@string/toggle_en_sym1" label=";"/>
+    </key>
+    <key label=",">
+      <toggle_state state_id="@string/toggle_en_sym1" label=","/>
+    </key>
+    <key label="?">
+      <toggle_state state_id="@string/toggle_en_sym1" label="\?"/>
+    </key>
+    <key id="3"/>
+  </row>
+
+  <row width="14.696%p" key_type="1">
+    <key code="-2" label="中 符" width="20%p" repeat="true">
+      <toggle_state state_id="@string/toggle_en_sym1" label="英 符" code="-2"/>
+    </key>
+    <key code="-3" key_type="3" label="\?123"/>
+    <key code="62" key_type="5" width="30.608%p"/>
+    <key label="。" key_type="0" icon="@drawable/period_full_icon"
+      icon_popup="@drawable/period_full_popup_icon">
+      <toggle_state state_id="@string/toggle_en_sym1" label="."
+        icon="@drawable/period_icon" icon_popup="@drawable/period_popup_icon"/>
+      <toggle_state state_id="@string/toggle_en_upper" label="."
+        icon="@drawable/period_icon" icon_popup="@drawable/period_popup_icon"/>
+    </key>
+    <key id="1"/>
+  </row>
+</keyboard>
+
diff --git a/PinyinIME/res/xml/skb_sym2.xml b/PinyinIME/res/xml/skb_sym2.xml
new file mode 100644
index 0000000..a55f91e
--- /dev/null
+++ b/PinyinIME/res/xml/skb_sym2.xml
@@ -0,0 +1,68 @@
+<?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.
+-->
+<keyboard
+  skb_template="@xml/skb_template1"
+  skb_cache_flag="true"
+  width="10%p"
+  height="25%p"
+  key_type="0"
+  repeat="false"
+  balloon="true">
+
+  <row>
+    <keys splitter="|" labels="~|±|×|÷|•|°|&lt;|&gt;|{|}"/>
+  </row>
+
+  <row>
+    <keys splitter="|" labels="©|£|€|^|®"/>
+    <key label="℃">
+      <toggle_state state_id="@string/toggle_en_sym2" label="℉"/>
+    </key>
+    <keys splitter="|" labels="_|="/>
+    <key label="【">
+      <toggle_state state_id="@string/toggle_en_sym2" label="["/>
+    </key>
+    <key label="】">
+      <toggle_state state_id="@string/toggle_en_sym2" label="]"/>
+    </key>
+  </row>
+
+  <row width="10.205%p">
+    <key code="-5" width="14.286%p" key_type="3" label="ALT"/>
+    <key label="™"/>
+    <key label="‘">
+      <toggle_state state_id="@string/toggle_en_sym2" label="‘"/>
+    </key>
+    <key label="’">
+      <toggle_state state_id="@string/toggle_en_sym2" label="’"/>
+    </key>
+    <keys splitter=" " labels="+ | \\ √"/>
+    <key id="3"/>
+  </row>
+
+  <row width="14.696%p" key_type="1">
+    <key code="-2" label="中 符" width="20%p" repeat="true">
+      <toggle_state state_id="@string/toggle_en_sym2" label="英 符" code="-2"/>
+    </key>
+    <key code="-3" key_type="3" label="\?123"/>
+    <key code="62" key_type="5" width="30.608%p"/>
+    <key label="……" key_type="0">
+      <toggle_state state_id="@string/toggle_en_sym2" label="…"/>
+    </key>
+    <key id="1"/>
+  </row>
+</keyboard>
+
diff --git a/PinyinIME/res/xml/skb_template1.xml b/PinyinIME/res/xml/skb_template1.xml
new file mode 100644
index 0000000..16338eb
--- /dev/null
+++ b/PinyinIME/res/xml/skb_template1.xml
@@ -0,0 +1,123 @@
+<?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.
+-->
+<skb_template
+  skb_bg="@drawable/skb_bg"
+  key_xmargin="0%p"
+  key_ymargin="0%p"
+  balloon_bg="@drawable/key_balloon_bg"
+  popup_bg="@drawable/miniskb_bg"
+  color="@color/label_color"
+  color_highlight="@color/label_color"
+  color_balloon="@color/balloon_color">
+  <!-- Normal key -->
+  <key_type
+    id="0"
+    bg="@drawable/normal_key_bg"
+    hlbg="@drawable/normal_key_hl_bg"
+    color_highlight="@color/label_color_hl0"/>
+
+  <!-- Function key -->
+  <key_type
+    id="1"
+    bg="@drawable/normal_key_bg"
+    hlbg="@drawable/normal_key_hl_bg"/>
+
+  <!-- Light key, light is off -->
+  <key_type
+    id="2"
+    bg="@drawable/light_key_bg"
+    hlbg="@drawable/light_key_hl_bg"/>
+
+  <!-- Light key, light is on -->
+  <key_type
+    id="3"
+    bg="@drawable/light_key_up_bg"
+    hlbg="@drawable/light_key_up_hl_bg"/>
+
+  <!-- key without background-->
+  <key_type
+    id="4"/>
+
+  <!-- Key with normal background but on-key high-light-->
+  <key_type
+    id="5"
+    bg="@drawable/normal_key_bg"
+    hlbg="@drawable/normal_key_hl_bg"
+    color_highlight="@color/label_color_hl0"/>
+
+  <!-- Default icons for enter key -->
+  <key_icon code="66" icon="@drawable/enter_icon"
+    icon_popup="@drawable/enter_popup_icon"/>
+
+  <!-- Default icons for space key -->
+  <key_icon code="62" icon="@drawable/space_icon"
+    icon_popup="@drawable/space_popup_icon"/>
+
+  <!-- Default icons for delete key -->
+  <key_icon code="67" icon="@drawable/delete_icon"
+    icon_popup="@drawable/delete_popup_icon"/>
+
+  <!-- Default key definition -->
+  <!-- Enter key for QWERTY-like keyboards.-->
+  <key id="1" start_pos_x="80%p" start_pos_y="75%p"
+    width="20%p" height="25%p" code="66" key_type="1">
+    <toggle_state state_id="@string/toggle_enter_go" label="去往" code="66"/>
+    <toggle_state state_id="@string/toggle_enter_search" code="66"
+      icon="@drawable/search_icon" icon_popup="@drawable/search_popup_icon"/>
+    <toggle_state state_id="@string/toggle_enter_send" label="发送" code="66"/>
+    <toggle_state state_id="@string/toggle_enter_next" label="下一个" code="66"/>
+    <toggle_state state_id="@string/toggle_enter_done" label="完成" code="66"/>
+  </key>
+
+  <!-- Enter key for phone keyboard.-->
+  <key id="2" start_pos_x="75%p" start_pos_y="75%p"
+    width="25%p" height="25%p" code="66" key_type="1" balloon="false">
+    <toggle_state state_id="@string/toggle_enter_go" label="去往" code="66"/>
+    <toggle_state state_id="@string/toggle_enter_search" code="66"
+      icon="@drawable/search_icon" icon_popup="@drawable/search_popup_icon"/>
+    <toggle_state state_id="@string/toggle_enter_send" label="发送" code="66"/>
+    <toggle_state state_id="@string/toggle_enter_next" label="下一个" code="66"/>
+    <toggle_state state_id="@string/toggle_enter_done" label="完成" code="66"/>
+  </key>
+
+  <!-- Delete key.-->
+  <key id="3" start_pos_x="85.715%p" start_pos_y="50%p"
+      width="14.286%p" height="25%p" code="67" key_type="1"
+      repeat="true"/>
+
+  <!-- Symbol-switching key (off-mode).-->
+  <key id="4" code="-3" start_pos_x="20%p" start_pos_y="75%p"
+      width="14.696%p" height="25%p" key_type="2" label="\?123"/>
+
+  <!-- Symbol-switching key (on-mode).-->
+  <key id="5" code="-3" start_pos_x="20%p" start_pos_y="75%p"
+      width="14.696%p" height="25%p" key_type="3" label="\?123"/>
+
+  <!-- Language-switching key. -->
+  <key id="6" start_pos_x="0%p" start_pos_y="75%p"
+      width="20%p" height="25%p" code="-2" label="中 文" key_type="1"
+      repeat="true">
+      <toggle_state state_id="@string/toggle_en_lower" label="英 文" code="-2"/>
+      <toggle_state state_id="@string/toggle_en_upper" label="英 文" code="-2"/>
+  </key>
+
+  <!-- Period key(English mode). -->
+  <key id="7" start_pos_x="65.304%p" start_pos_y="75%p"
+      width="14.696%p" height="25%p" label="." key_type="0"
+      icon="@drawable/period_icon" icon_popup="@drawable/period_popup_icon"/>
+
+</skb_template>
+
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/BalloonHint.java b/PinyinIME/src/com/android/inputmethod/pinyin/BalloonHint.java
new file mode 100644
index 0000000..919dbf1
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/BalloonHint.java
@@ -0,0 +1,472 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.view.Gravity;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.widget.PopupWindow;
+
+/**
+ * Subclass of PopupWindow used as the feedback when user presses on a soft key
+ * or a candidate.
+ */
+public class BalloonHint extends PopupWindow {
+    /**
+     * Delayed time to show the balloon hint.
+     */
+    public static final int TIME_DELAY_SHOW = 0;
+
+    /**
+     * Delayed time to dismiss the balloon hint.
+     */
+    public static final int TIME_DELAY_DISMISS = 200;
+
+    /**
+     * The padding information of the balloon. Because PopupWindow's background
+     * can not be changed unless it is dismissed and shown again, we set the
+     * real background drawable to the content view, and make the PopupWindow's
+     * background transparent. So actually this padding information is for the
+     * content view.
+     */
+    private Rect mPaddingRect = new Rect();
+
+    /**
+     * The context used to create this balloon hint object.
+     */
+    private Context mContext;
+
+    /**
+     * Parent used to show the balloon window.
+     */
+    private View mParent;
+
+    /**
+     * The content view of the balloon.
+     */
+    BalloonView mBalloonView;
+
+    /**
+     * The measuring specification used to determine its size. Key-press
+     * balloons and candidates balloons have different measuring specifications.
+     */
+    private int mMeasureSpecMode;
+
+    /**
+     * Used to indicate whether the balloon needs to be dismissed forcibly.
+     */
+    private boolean mForceDismiss;
+
+    /**
+     * Timer used to show/dismiss the balloon window with some time delay.
+     */
+    private BalloonTimer mBalloonTimer;
+
+    private int mParentLocationInWindow[] = new int[2];
+
+    public BalloonHint(Context context, View parent, int measureSpecMode) {
+        super(context);
+        mParent = parent;
+        mMeasureSpecMode = measureSpecMode;
+
+        setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+        setTouchable(false);
+        setBackgroundDrawable(new ColorDrawable(0));
+
+        mBalloonView = new BalloonView(context);
+        mBalloonView.setClickable(false);
+        setContentView(mBalloonView);
+
+        mBalloonTimer = new BalloonTimer();
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public Rect getPadding() {
+        return mPaddingRect;
+    }
+
+    public void setBalloonBackground(Drawable drawable) {
+        // We usually pick up a background from a soft keyboard template,
+        // and the object may has been set to this balloon before.
+        if (mBalloonView.getBackground() == drawable) return;
+        mBalloonView.setBackgroundDrawable(drawable);
+
+        if (null != drawable) {
+            drawable.getPadding(mPaddingRect);
+        } else {
+            mPaddingRect.set(0, 0, 0, 0);
+        }
+    }
+
+    /**
+     * Set configurations to show text label in this balloon.
+     *
+     * @param label The text label to show in the balloon.
+     * @param textSize The text size used to show label.
+     * @param textBold Used to indicate whether the label should be bold.
+     * @param textColor The text color used to show label.
+     * @param width The desired width of the balloon. The real width is
+     *        determined by the desired width and balloon's measuring
+     *        specification.
+     * @param height The desired width of the balloon. The real width is
+     *        determined by the desired width and balloon's measuring
+     *        specification.
+     */
+    public void setBalloonConfig(String label, float textSize,
+            boolean textBold, int textColor, int width, int height) {
+        mBalloonView.setTextConfig(label, textSize, textBold, textColor);
+        setBalloonSize(width, height);
+    }
+
+    /**
+     * Set configurations to show text label in this balloon.
+     *
+     * @param icon The icon used to shown in this balloon.
+     * @param width The desired width of the balloon. The real width is
+     *        determined by the desired width and balloon's measuring
+     *        specification.
+     * @param height The desired width of the balloon. The real width is
+     *        determined by the desired width and balloon's measuring
+     *        specification.
+     */
+    public void setBalloonConfig(Drawable icon, int width, int height) {
+        mBalloonView.setIcon(icon);
+        setBalloonSize(width, height);
+    }
+
+
+    public boolean needForceDismiss() {
+        return mForceDismiss;
+    }
+
+    public int getPaddingLeft() {
+        return mPaddingRect.left;
+    }
+
+    public int getPaddingTop() {
+        return mPaddingRect.top;
+    }
+
+    public int getPaddingRight() {
+        return mPaddingRect.right;
+    }
+
+    public int getPaddingBottom() {
+        return mPaddingRect.bottom;
+    }
+
+    public void delayedShow(long delay, int locationInParent[]) {
+        if (mBalloonTimer.isPending()) {
+            mBalloonTimer.removeTimer();
+        }
+        if (delay <= 0) {
+            mParent.getLocationInWindow(mParentLocationInWindow);
+            showAtLocation(mParent, Gravity.LEFT | Gravity.TOP,
+                    locationInParent[0], locationInParent[1]
+                            + mParentLocationInWindow[1]);
+        } else {
+            mBalloonTimer.startTimer(delay, BalloonTimer.ACTION_SHOW,
+                    locationInParent, -1, -1);
+        }
+    }
+
+    public void delayedUpdate(long delay, int locationInParent[],
+            int width, int height) {
+        mBalloonView.invalidate();
+        if (mBalloonTimer.isPending()) {
+            mBalloonTimer.removeTimer();
+        }
+        if (delay <= 0) {
+            mParent.getLocationInWindow(mParentLocationInWindow);
+            update(locationInParent[0], locationInParent[1]
+                    + mParentLocationInWindow[1], width, height);
+        } else {
+            mBalloonTimer.startTimer(delay, BalloonTimer.ACTION_UPDATE,
+                    locationInParent, width, height);
+        }
+    }
+
+    public void delayedDismiss(long delay) {
+        if (mBalloonTimer.isPending()) {
+            mBalloonTimer.removeTimer();
+            int pendingAction = mBalloonTimer.getAction();
+            if (0 != delay && BalloonTimer.ACTION_HIDE != pendingAction) {
+                mBalloonTimer.run();
+            }
+        }
+        if (delay <= 0) {
+            dismiss();
+        } else {
+            mBalloonTimer.startTimer(delay, BalloonTimer.ACTION_HIDE, null, -1,
+                    -1);
+        }
+    }
+
+    public void removeTimer() {
+        if (mBalloonTimer.isPending()) {
+            mBalloonTimer.removeTimer();
+        }
+    }
+
+    private void setBalloonSize(int width, int height) {
+        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
+                mMeasureSpecMode);
+        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
+                mMeasureSpecMode);
+        mBalloonView.measure(widthMeasureSpec, heightMeasureSpec);
+
+        int oldWidth = getWidth();
+        int oldHeight = getHeight();
+        int newWidth = mBalloonView.getMeasuredWidth() + getPaddingLeft()
+                + getPaddingRight();
+        int newHeight = mBalloonView.getMeasuredHeight() + getPaddingTop()
+                + getPaddingBottom();
+        setWidth(newWidth);
+        setHeight(newHeight);
+
+        // If update() is called to update both size and position, the system
+        // will first MOVE the PopupWindow to the new position, and then
+        // perform a size-updating operation, so there will be a flash in
+        // PopupWindow if user presses a key and moves finger to next one whose
+        // size is different.
+        // PopupWindow will handle the updating issue in one go in the future,
+        // but before that, if we find the size is changed, a mandatory dismiss
+        // operation is required. In our UI design, normal QWERTY keys' width
+        // can be different in 1-pixel, and we do not dismiss the balloon when
+        // user move between QWERTY keys.
+        mForceDismiss = false;
+        if (isShowing()) {
+            mForceDismiss = oldWidth - newWidth > 1 || newWidth - oldWidth > 1;
+        }
+    }
+
+
+    private class BalloonTimer extends Handler implements Runnable {
+        public static final int ACTION_SHOW = 1;
+        public static final int ACTION_HIDE = 2;
+        public static final int ACTION_UPDATE = 3;
+
+        /**
+         * The pending action.
+         */
+        private int mAction;
+
+        private int mPositionInParent[] = new int[2];
+        private int mWidth;
+        private int mHeight;
+
+        private boolean mTimerPending = false;
+
+        public void startTimer(long time, int action, int positionInParent[],
+                int width, int height) {
+            mAction = action;
+            if (ACTION_HIDE != action) {
+                mPositionInParent[0] = positionInParent[0];
+                mPositionInParent[1] = positionInParent[1];
+            }
+            mWidth = width;
+            mHeight = height;
+            postDelayed(this, time);
+            mTimerPending = true;
+        }
+
+        public boolean isPending() {
+            return mTimerPending;
+        }
+
+        public boolean removeTimer() {
+            if (mTimerPending) {
+                mTimerPending = false;
+                removeCallbacks(this);
+                return true;
+            }
+
+            return false;
+        }
+
+        public int getAction() {
+            return mAction;
+        }
+
+        public void run() {
+            switch (mAction) {
+            case ACTION_SHOW:
+                mParent.getLocationInWindow(mParentLocationInWindow);
+                showAtLocation(mParent, Gravity.LEFT | Gravity.TOP,
+                        mPositionInParent[0], mPositionInParent[1]
+                                + mParentLocationInWindow[1]);
+                break;
+            case ACTION_HIDE:
+                dismiss();
+                break;
+            case ACTION_UPDATE:
+                mParent.getLocationInWindow(mParentLocationInWindow);
+                update(mPositionInParent[0], mPositionInParent[1]
+                        + mParentLocationInWindow[1], mWidth, mHeight);
+            }
+            mTimerPending = false;
+        }
+    }
+
+    private class BalloonView extends View {
+        /**
+         * Suspension points used to display long items.
+         */
+        private static final String SUSPENSION_POINTS = "...";
+
+        /**
+         * The icon to be shown. If it is not null, {@link #mLabel} will be
+         * ignored.
+         */
+        private Drawable mIcon;
+
+        /**
+         * The label to be shown. It is enabled only if {@link #mIcon} is null.
+         */
+        private String mLabel;
+
+        private int mLabeColor = 0xff000000;
+        private Paint mPaintLabel;
+        private FontMetricsInt mFmi;
+
+        /**
+         * The width to show suspension points.
+         */
+        private float mSuspensionPointsWidth;
+
+
+        public BalloonView(Context context) {
+            super(context);
+            mPaintLabel = new Paint();
+            mPaintLabel.setColor(mLabeColor);
+            mPaintLabel.setAntiAlias(true);
+            mPaintLabel.setFakeBoldText(true);
+            mFmi = mPaintLabel.getFontMetricsInt();
+        }
+
+        public void setIcon(Drawable icon) {
+            mIcon = icon;
+        }
+
+        public void setTextConfig(String label, float fontSize,
+                boolean textBold, int textColor) {
+            // Icon should be cleared so that the label will be enabled.
+            mIcon = null;
+            mLabel = label;
+            mPaintLabel.setTextSize(fontSize);
+            mPaintLabel.setFakeBoldText(textBold);
+            mPaintLabel.setColor(textColor);
+            mFmi = mPaintLabel.getFontMetricsInt();
+            mSuspensionPointsWidth = mPaintLabel.measureText(SUSPENSION_POINTS);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+            final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+            final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+            if (widthMode == MeasureSpec.EXACTLY) {
+                setMeasuredDimension(widthSize, heightSize);
+                return;
+            }
+
+            int measuredWidth = mPaddingLeft + mPaddingRight;
+            int measuredHeight = mPaddingTop + mPaddingBottom;
+            if (null != mIcon) {
+                measuredWidth += mIcon.getIntrinsicWidth();
+                measuredHeight += mIcon.getIntrinsicHeight();
+            } else if (null != mLabel) {
+                measuredWidth += (int) (mPaintLabel.measureText(mLabel));
+                measuredHeight += mFmi.bottom - mFmi.top;
+            }
+            if (widthSize > measuredWidth || widthMode == MeasureSpec.AT_MOST) {
+                measuredWidth = widthSize;
+            }
+
+            if (heightSize > measuredHeight
+                    || heightMode == MeasureSpec.AT_MOST) {
+                measuredHeight = heightSize;
+            }
+
+            int maxWidth = Environment.getInstance().getScreenWidth() -
+                    mPaddingLeft - mPaddingRight;
+            if (measuredWidth > maxWidth) {
+                measuredWidth = maxWidth;
+            }
+            setMeasuredDimension(measuredWidth, measuredHeight);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            int width = getWidth();
+            int height = getHeight();
+            if (null != mIcon) {
+                int marginLeft = (width - mIcon.getIntrinsicWidth()) / 2;
+                int marginRight = width - mIcon.getIntrinsicWidth()
+                        - marginLeft;
+                int marginTop = (height - mIcon.getIntrinsicHeight()) / 2;
+                int marginBottom = height - mIcon.getIntrinsicHeight()
+                        - marginTop;
+                mIcon.setBounds(marginLeft, marginTop, width - marginRight,
+                        height - marginBottom);
+                mIcon.draw(canvas);
+            } else if (null != mLabel) {
+                float labelMeasuredWidth = mPaintLabel.measureText(mLabel);
+                float x = mPaddingLeft;
+                x += (width - labelMeasuredWidth - mPaddingLeft - mPaddingRight) / 2.0f;
+                String labelToDraw = mLabel;
+                if (x < mPaddingLeft) {
+                    x = mPaddingLeft;
+                    labelToDraw = getLimitedLabelForDrawing(mLabel,
+                            width - mPaddingLeft - mPaddingRight);
+                }
+
+                int fontHeight = mFmi.bottom - mFmi.top;
+                float marginY = (height - fontHeight) / 2.0f;
+                float y = marginY - mFmi.top;
+                canvas.drawText(labelToDraw, x, y, mPaintLabel);
+            }
+        }
+
+        private String getLimitedLabelForDrawing(String rawLabel,
+                float widthToDraw) {
+            int subLen = rawLabel.length();
+            if (subLen <= 1) return rawLabel;
+            do {
+                subLen--;
+                float width = mPaintLabel.measureText(rawLabel, 0, subLen);
+                if (width + mSuspensionPointsWidth <= widthToDraw || 1 >= subLen) {
+                    return rawLabel.substring(0, subLen) +
+                            SUSPENSION_POINTS;
+                }
+            } while (true);
+        }
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/CandidateView.java b/PinyinIME/src/com/android/inputmethod/pinyin/CandidateView.java
new file mode 100644
index 0000000..8dc1bf1
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/CandidateView.java
@@ -0,0 +1,760 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import com.android.inputmethod.pinyin.PinyinIME.DecodingInfo;
+
+import java.util.Vector;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * View to show candidate list. There two candidate view instances which are
+ * used to show animation when user navigates between pages.
+ */
+public class CandidateView extends View {
+    /**
+     * The minimum width to show a item.
+     */
+    private static final float MIN_ITEM_WIDTH = 22;
+
+    /**
+     * Suspension points used to display long items.
+     */
+    private static final String SUSPENSION_POINTS = "...";
+
+    /**
+     * The width to draw candidates.
+     */
+    private int mContentWidth;
+
+    /**
+     * The height to draw candidate content.
+     */
+    private int mContentHeight;
+
+    /**
+     * Whether footnotes are displayed. Footnote is shown when hardware keyboard
+     * is available.
+     */
+    private boolean mShowFootnote = true;
+
+    /**
+     * Balloon hint for candidate press/release.
+     */
+    private BalloonHint mBalloonHint;
+
+    /**
+     * Desired position of the balloon to the input view.
+     */
+    private int mHintPositionToInputView[] = new int[2];
+
+    /**
+     * Decoding result to show.
+     */
+    private DecodingInfo mDecInfo;
+
+    /**
+     * Listener used to notify IME that user clicks a candidate, or navigate
+     * between them.
+     */
+    private CandidateViewListener mCvListener;
+
+    /**
+     * Used to notify the container to update the status of forward/backward
+     * arrows.
+     */
+    private ArrowUpdater mArrowUpdater;
+
+    /**
+     * If true, update the arrow status when drawing candidates.
+     */
+    private boolean mUpdateArrowStatusWhenDraw = false;
+
+    /**
+     * Page number of the page displayed in this view.
+     */
+    private int mPageNo;
+
+    /**
+     * Active candidate position in this page.
+     */
+    private int mActiveCandInPage;
+
+    /**
+     * Used to decided whether the active candidate should be highlighted or
+     * not. If user changes focus to composing view (The view to show Pinyin
+     * string), the highlight in candidate view should be removed.
+     */
+    private boolean mEnableActiveHighlight = true;
+
+    /**
+     * The page which is just calculated.
+     */
+    private int mPageNoCalculated = -1;
+
+    /**
+     * The Drawable used to display as the background of the high-lighted item.
+     */
+    private Drawable mActiveCellDrawable;
+
+    /**
+     * The Drawable used to display as separators between candidates.
+     */
+    private Drawable mSeparatorDrawable;
+
+    /**
+     * Color to draw normal candidates generated by IME.
+     */
+    private int mImeCandidateColor;
+
+    /**
+     * Color to draw normal candidates Recommended by application.
+     */
+    private int mRecommendedCandidateColor;
+
+    /**
+     * Color to draw the normal(not highlighted) candidates, it can be one of
+     * {@link #mImeCandidateColor} or {@link #mRecommendedCandidateColor}.
+     */
+    private int mNormalCandidateColor;
+
+    /**
+     * Color to draw the active(highlighted) candidates, including candidates
+     * from IME and candidates from application.
+     */
+    private int mActiveCandidateColor;
+
+    /**
+     * Text size to draw candidates generated by IME.
+     */
+    private int mImeCandidateTextSize;
+
+    /**
+     * Text size to draw candidates recommended by application.
+     */
+    private int mRecommendedCandidateTextSize;
+
+    /**
+     * The current text size to draw candidates. It can be one of
+     * {@link #mImeCandidateTextSize} or {@link #mRecommendedCandidateTextSize}.
+     */
+    private int mCandidateTextSize;
+
+    /**
+     * Paint used to draw candidates.
+     */
+    private Paint mCandidatesPaint;
+
+    /**
+     * Used to draw footnote.
+     */
+    private Paint mFootnotePaint;
+
+    /**
+     * The width to show suspension points.
+     */
+    private float mSuspensionPointsWidth;
+
+    /**
+     * Rectangle used to draw the active candidate.
+     */
+    private RectF mActiveCellRect;
+
+    /**
+     * Left and right margins for a candidate. It is specified in xml, and is
+     * the minimum margin for a candidate. The actual gap between two candidates
+     * is 2 * {@link #mCandidateMargin} + {@link #mSeparatorDrawable}.
+     * getIntrinsicWidth(). Because length of candidate is not fixed, there can
+     * be some extra space after the last candidate in the current page. In
+     * order to achieve best look-and-feel, this extra space will be divided and
+     * allocated to each candidates.
+     */
+    private float mCandidateMargin;
+
+    /**
+     * Left and right extra margins for a candidate.
+     */
+    private float mCandidateMarginExtra;
+
+    /**
+     * Rectangles for the candidates in this page.
+     **/
+    private Vector<RectF> mCandRects;
+
+    /**
+     * FontMetricsInt used to measure the size of candidates.
+     */
+    private FontMetricsInt mFmiCandidates;
+
+    /**
+     * FontMetricsInt used to measure the size of footnotes.
+     */
+    private FontMetricsInt mFmiFootnote;
+
+    private PressTimer mTimer = new PressTimer();
+
+    private GestureDetector mGestureDetector;
+
+    private int mLocationTmp[] = new int[2];
+
+    public CandidateView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        Resources r = context.getResources();
+
+        Configuration conf = r.getConfiguration();
+        if (conf.keyboard == Configuration.KEYBOARD_NOKEYS
+                || conf.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
+            mShowFootnote = false;
+        }
+
+        mActiveCellDrawable = r.getDrawable(R.drawable.candidate_hl_bg);
+        mSeparatorDrawable = r.getDrawable(R.drawable.candidates_vertical_line);
+        mCandidateMargin = r.getDimension(R.dimen.candidate_margin_left_right);
+
+        mImeCandidateColor = r.getColor(R.color.candidate_color);
+        mRecommendedCandidateColor = r.getColor(R.color.recommended_candidate_color);
+        mNormalCandidateColor = mImeCandidateColor;
+        mActiveCandidateColor = r.getColor(R.color.active_candidate_color);
+
+        mCandidatesPaint = new Paint();
+        mCandidatesPaint.setAntiAlias(true);
+
+        mFootnotePaint = new Paint();
+        mFootnotePaint.setAntiAlias(true);
+        mFootnotePaint.setColor(r.getColor(R.color.footnote_color));
+        mActiveCellRect = new RectF();
+
+        mCandRects = new Vector<RectF>();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int mOldWidth = mMeasuredWidth;
+        int mOldHeight = mMeasuredHeight;
+
+        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
+                widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(),
+                heightMeasureSpec));
+
+        if (mOldWidth != mMeasuredWidth || mOldHeight != mMeasuredHeight) {
+            onSizeChanged();
+        }
+    }
+
+    public void initialize(ArrowUpdater arrowUpdater, BalloonHint balloonHint,
+            GestureDetector gestureDetector, CandidateViewListener cvListener) {
+        mArrowUpdater = arrowUpdater;
+        mBalloonHint = balloonHint;
+        mGestureDetector = gestureDetector;
+        mCvListener = cvListener;
+    }
+
+    public void setDecodingInfo(DecodingInfo decInfo) {
+        if (null == decInfo) return;
+        mDecInfo = decInfo;
+        mPageNoCalculated = -1;
+
+        if (mDecInfo.candidatesFromApp()) {
+            mNormalCandidateColor = mRecommendedCandidateColor;
+            mCandidateTextSize = mRecommendedCandidateTextSize;
+        } else {
+            mNormalCandidateColor = mImeCandidateColor;
+            mCandidateTextSize = mImeCandidateTextSize;
+        }
+        if (mCandidatesPaint.getTextSize() != mCandidateTextSize) {
+            mCandidatesPaint.setTextSize(mCandidateTextSize);
+            mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
+            mSuspensionPointsWidth =
+                    mCandidatesPaint.measureText(SUSPENSION_POINTS);
+        }
+
+        // Remove any pending timer for the previous list.
+        mTimer.removeTimer();
+    }
+
+    public int getActiveCandiatePosInPage() {
+        return mActiveCandInPage;
+    }
+
+    public int getActiveCandiatePosGlobal() {
+        return mDecInfo.mPageStart.get(mPageNo) + mActiveCandInPage;
+    }
+
+    /**
+     * Show a page in the decoding result set previously.
+     *
+     * @param pageNo Which page to show.
+     * @param activeCandInPage Which candidate should be set as active item.
+     * @param enableActiveHighlight When false, active item will not be
+     *        highlighted.
+     */
+    public void showPage(int pageNo, int activeCandInPage,
+            boolean enableActiveHighlight) {
+        if (null == mDecInfo) return;
+        mPageNo = pageNo;
+        mActiveCandInPage = activeCandInPage;
+        if (mEnableActiveHighlight != enableActiveHighlight) {
+            mEnableActiveHighlight = enableActiveHighlight;
+        }
+
+        if (!calculatePage(mPageNo)) {
+            mUpdateArrowStatusWhenDraw = true;
+        } else {
+            mUpdateArrowStatusWhenDraw = false;
+        }
+
+        invalidate();
+    }
+
+    public void enableActiveHighlight(boolean enableActiveHighlight) {
+        if (enableActiveHighlight == mEnableActiveHighlight) return;
+
+        mEnableActiveHighlight = enableActiveHighlight;
+        invalidate();
+    }
+
+    public boolean activeCursorForward() {
+        if (!mDecInfo.pageReady(mPageNo)) return false;
+        int pageSize = mDecInfo.mPageStart.get(mPageNo + 1)
+                - mDecInfo.mPageStart.get(mPageNo);
+        if (mActiveCandInPage + 1 < pageSize) {
+            showPage(mPageNo, mActiveCandInPage + 1, true);
+            return true;
+        }
+        return false;
+    }
+
+    public boolean activeCurseBackward() {
+        if (mActiveCandInPage > 0) {
+            showPage(mPageNo, mActiveCandInPage - 1, true);
+            return true;
+        }
+        return false;
+    }
+
+    private void onSizeChanged() {
+        mContentWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+        mContentHeight = (int) ((mMeasuredHeight - mPaddingTop - mPaddingBottom) * 0.95f);
+        /**
+         * How to decide the font size if the height for display is given?
+         * Now it is implemented in a stupid way.
+         */
+        int textSize = 1;
+        mCandidatesPaint.setTextSize(textSize);
+        mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
+        while (mFmiCandidates.bottom - mFmiCandidates.top < mContentHeight) {
+            textSize++;
+            mCandidatesPaint.setTextSize(textSize);
+            mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
+        }
+
+        mImeCandidateTextSize = textSize;
+        mRecommendedCandidateTextSize = textSize * 3 / 4;
+        if (null == mDecInfo) {
+            mCandidateTextSize = mImeCandidateTextSize;
+            mCandidatesPaint.setTextSize(mCandidateTextSize);
+            mFmiCandidates = mCandidatesPaint.getFontMetricsInt();
+            mSuspensionPointsWidth =
+                mCandidatesPaint.measureText(SUSPENSION_POINTS);
+        } else {
+            // Reset the decoding information to update members for painting.
+            setDecodingInfo(mDecInfo);
+        }
+
+        textSize = 1;
+        mFootnotePaint.setTextSize(textSize);
+        mFmiFootnote = mFootnotePaint.getFontMetricsInt();
+        while (mFmiFootnote.bottom - mFmiFootnote.top < mContentHeight / 2) {
+            textSize++;
+            mFootnotePaint.setTextSize(textSize);
+            mFmiFootnote = mFootnotePaint.getFontMetricsInt();
+        }
+        textSize--;
+        mFootnotePaint.setTextSize(textSize);
+        mFmiFootnote = mFootnotePaint.getFontMetricsInt();
+
+        // When the size is changed, the first page will be displayed.
+        mPageNo = 0;
+        mActiveCandInPage = 0;
+    }
+
+    private boolean calculatePage(int pageNo) {
+        if (pageNo == mPageNoCalculated) return true;
+
+        mContentWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+        mContentHeight = (int) ((mMeasuredHeight - mPaddingTop - mPaddingBottom) * 0.95f);
+
+        if (mContentWidth <= 0 || mContentHeight <= 0) return false;
+
+        int candSize = mDecInfo.mCandidatesList.size();
+
+        // If the size of page exists, only calculate the extra margin.
+        boolean onlyExtraMargin = false;
+        int fromPage = mDecInfo.mPageStart.size() - 1;
+        if (mDecInfo.mPageStart.size() > pageNo + 1) {
+            onlyExtraMargin = true;
+            fromPage = pageNo;
+        }
+
+        // If the previous pages have no information, calculate them first.
+        for (int p = fromPage; p <= pageNo; p++) {
+            int pStart = mDecInfo.mPageStart.get(p);
+            int pSize = 0;
+            int charNum = 0;
+            float lastItemWidth = 0;
+
+            float xPos;
+            xPos = 0;
+            xPos += mSeparatorDrawable.getIntrinsicWidth();
+            while (xPos < mContentWidth && pStart + pSize < candSize) {
+                int itemPos = pStart + pSize;
+                String itemStr = mDecInfo.mCandidatesList.get(itemPos);
+                float itemWidth = mCandidatesPaint.measureText(itemStr);
+                if (itemWidth < MIN_ITEM_WIDTH) itemWidth = MIN_ITEM_WIDTH;
+
+                itemWidth += mCandidateMargin * 2;
+                itemWidth += mSeparatorDrawable.getIntrinsicWidth();
+                if (xPos + itemWidth < mContentWidth || 0 == pSize) {
+                    xPos += itemWidth;
+                    lastItemWidth = itemWidth;
+                    pSize++;
+                    charNum += itemStr.length();
+                } else {
+                    break;
+                }
+            }
+            if (!onlyExtraMargin) {
+                mDecInfo.mPageStart.add(pStart + pSize);
+                mDecInfo.mCnToPage.add(mDecInfo.mCnToPage.get(p) + charNum);
+            }
+
+            float marginExtra = (mContentWidth - xPos) / pSize / 2;
+
+            if (mContentWidth - xPos > lastItemWidth) {
+                // Must be the last page, because if there are more items,
+                // the next item's width must be less than lastItemWidth.
+                // In this case, if the last margin is less than the current
+                // one, the last margin can be used, so that the
+                // look-and-feeling will be the same as the previous page.
+                if (mCandidateMarginExtra <= marginExtra) {
+                    marginExtra = mCandidateMarginExtra;
+                }
+            } else if (pSize == 1) {
+                marginExtra = 0;
+            }
+            mCandidateMarginExtra = marginExtra;
+        }
+        mPageNoCalculated = pageNo;
+        return true;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        // The invisible candidate view(the one which is not in foreground) can
+        // also be called to drawn, but its decoding result and candidate list
+        // may be empty.
+        if (null == mDecInfo || mDecInfo.isCandidatesListEmpty()) return;
+
+        // Calculate page. If the paging information is ready, the function will
+        // return at once.
+        calculatePage(mPageNo);
+
+        int pStart = mDecInfo.mPageStart.get(mPageNo);
+        int pSize = mDecInfo.mPageStart.get(mPageNo + 1) - pStart;
+        float candMargin = mCandidateMargin + mCandidateMarginExtra;
+        if (mActiveCandInPage > pSize - 1) {
+            mActiveCandInPage = pSize - 1;
+        }
+
+        mCandRects.removeAllElements();
+
+        float xPos = mPaddingLeft;
+        int yPos = (getMeasuredHeight() -
+                (mFmiCandidates.bottom - mFmiCandidates.top)) / 2
+                - mFmiCandidates.top;
+        xPos += drawVerticalSeparator(canvas, xPos);
+        for (int i = 0; i < pSize; i++) {
+            float footnoteSize = 0;
+            String footnote = null;
+            if (mShowFootnote) {
+                footnote = Integer.toString(i + 1);
+                footnoteSize = mFootnotePaint.measureText(footnote);
+                assert (footnoteSize < candMargin);
+            }
+            String cand = mDecInfo.mCandidatesList.get(pStart + i);
+            float candidateWidth = mCandidatesPaint.measureText(cand);
+            float centerOffset = 0;
+            if (candidateWidth < MIN_ITEM_WIDTH) {
+                centerOffset = (MIN_ITEM_WIDTH - candidateWidth) / 2;
+                candidateWidth = MIN_ITEM_WIDTH;
+            }
+
+            float itemTotalWidth = candidateWidth + 2 * candMargin;
+
+            if (mActiveCandInPage == i && mEnableActiveHighlight) {
+                mActiveCellRect.set(xPos, mPaddingTop + 1, xPos
+                        + itemTotalWidth, getHeight() - mPaddingBottom - 1);
+                mActiveCellDrawable.setBounds((int) mActiveCellRect.left,
+                        (int) mActiveCellRect.top, (int) mActiveCellRect.right,
+                        (int) mActiveCellRect.bottom);
+                mActiveCellDrawable.draw(canvas);
+            }
+
+            if (mCandRects.size() < pSize) mCandRects.add(new RectF());
+            mCandRects.elementAt(i).set(xPos - 1, yPos + mFmiCandidates.top,
+                    xPos + itemTotalWidth + 1, yPos + mFmiCandidates.bottom);
+
+            // Draw footnote
+            if (mShowFootnote) {
+                canvas.drawText(footnote, xPos + (candMargin - footnoteSize)
+                        / 2, yPos, mFootnotePaint);
+            }
+
+            // Left margin
+            xPos += candMargin;
+            if (candidateWidth > mContentWidth - xPos - centerOffset) {
+                cand = getLimitedCandidateForDrawing(cand,
+                        mContentWidth - xPos - centerOffset);
+            }
+            if (mActiveCandInPage == i && mEnableActiveHighlight) {
+                mCandidatesPaint.setColor(mActiveCandidateColor);
+            } else {
+                mCandidatesPaint.setColor(mNormalCandidateColor);
+            }
+            canvas.drawText(cand, xPos + centerOffset, yPos,
+                    mCandidatesPaint);
+
+            // Candidate and right margin
+            xPos += candidateWidth + candMargin;
+
+            // Draw the separator between candidates.
+            xPos += drawVerticalSeparator(canvas, xPos);
+        }
+
+        // Update the arrow status of the container.
+        if (null != mArrowUpdater && mUpdateArrowStatusWhenDraw) {
+            mArrowUpdater.updateArrowStatus();
+            mUpdateArrowStatusWhenDraw = false;
+        }
+    }
+
+    private String getLimitedCandidateForDrawing(String rawCandidate,
+            float widthToDraw) {
+        int subLen = rawCandidate.length();
+        if (subLen <= 1) return rawCandidate;
+        do {
+            subLen--;
+            float width = mCandidatesPaint.measureText(rawCandidate, 0, subLen);
+            if (width + mSuspensionPointsWidth <= widthToDraw || 1 >= subLen) {
+                return rawCandidate.substring(0, subLen) +
+                        SUSPENSION_POINTS;
+            }
+        } while (true);
+    }
+
+    private float drawVerticalSeparator(Canvas canvas, float xPos) {
+        mSeparatorDrawable.setBounds((int) xPos, mPaddingTop, (int) xPos
+                + mSeparatorDrawable.getIntrinsicWidth(), getMeasuredHeight()
+                - mPaddingBottom);
+        mSeparatorDrawable.draw(canvas);
+        return mSeparatorDrawable.getIntrinsicWidth();
+    }
+
+    private int mapToItemInPage(int x, int y) {
+        // mCandRects.size() == 0 happens when the page is set, but
+        // touch events occur before onDraw(). It usually happens with
+        // monkey test.
+        if (!mDecInfo.pageReady(mPageNo) || mPageNoCalculated != mPageNo
+                || mCandRects.size() == 0) {
+            return -1;
+        }
+
+        int pageStart = mDecInfo.mPageStart.get(mPageNo);
+        int pageSize = mDecInfo.mPageStart.get(mPageNo + 1) - pageStart;
+        if (mCandRects.size() < pageSize) {
+            return -1;
+        }
+
+        // If not found, try to find the nearest one.
+        float nearestDis = Float.MAX_VALUE;
+        int nearest = -1;
+        for (int i = 0; i < pageSize; i++) {
+            RectF r = mCandRects.elementAt(i);
+            if (r.left < x && r.right > x && r.top < y && r.bottom > y) {
+                return i;
+            }
+            float disx = (r.left + r.right) / 2 - x;
+            float disy = (r.top + r.bottom) / 2 - y;
+            float dis = disx * disx + disy * disy;
+            if (dis < nearestDis) {
+                nearestDis = dis;
+                nearest = i;
+            }
+        }
+
+        return nearest;
+    }
+
+    // Because the candidate view under the current focused one may also get
+    // touching events. Here we just bypass the event to the container and let
+    // it decide which view should handle the event.
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return super.onTouchEvent(event);
+    }
+
+    public boolean onTouchEventReal(MotionEvent event) {
+        // The page in the background can also be touched.
+        if (null == mDecInfo || !mDecInfo.pageReady(mPageNo)
+                || mPageNoCalculated != mPageNo) return true;
+
+        int x, y;
+        x = (int) event.getX();
+        y = (int) event.getY();
+
+        if (mGestureDetector.onTouchEvent(event)) {
+            mTimer.removeTimer();
+            mBalloonHint.delayedDismiss(0);
+            return true;
+        }
+
+        int clickedItemInPage = -1;
+
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_UP:
+            clickedItemInPage = mapToItemInPage(x, y);
+            if (clickedItemInPage >= 0) {
+                invalidate();
+                mCvListener.onClickChoice(clickedItemInPage
+                        + mDecInfo.mPageStart.get(mPageNo));
+            }
+            mBalloonHint.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
+            break;
+
+        case MotionEvent.ACTION_DOWN:
+            clickedItemInPage = mapToItemInPage(x, y);
+            if (clickedItemInPage >= 0) {
+                showBalloon(clickedItemInPage, true);
+                mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,
+                        clickedItemInPage);
+            }
+            break;
+
+        case MotionEvent.ACTION_CANCEL:
+            break;
+
+        case MotionEvent.ACTION_MOVE:
+            clickedItemInPage = mapToItemInPage(x, y);
+            if (clickedItemInPage >= 0
+                    && (clickedItemInPage != mTimer.getActiveCandOfPageToShow() || mPageNo != mTimer
+                            .getPageToShow())) {
+                showBalloon(clickedItemInPage, true);
+                mTimer.startTimer(BalloonHint.TIME_DELAY_SHOW, mPageNo,
+                        clickedItemInPage);
+            }
+        }
+        return true;
+    }
+
+    private void showBalloon(int candPos, boolean delayedShow) {
+        mBalloonHint.removeTimer();
+
+        RectF r = mCandRects.elementAt(candPos);
+        int desired_width = (int) (r.right - r.left);
+        int desired_height = (int) (r.bottom - r.top);
+        mBalloonHint.setBalloonConfig(mDecInfo.mCandidatesList
+                .get(mDecInfo.mPageStart.get(mPageNo) + candPos), 44, true,
+                mImeCandidateColor, desired_width, desired_height);
+
+        getLocationOnScreen(mLocationTmp);
+        mHintPositionToInputView[0] = mLocationTmp[0]
+                + (int) (r.left - (mBalloonHint.getWidth() - desired_width) / 2);
+        mHintPositionToInputView[1] = -mBalloonHint.getHeight();
+
+        long delay = BalloonHint.TIME_DELAY_SHOW;
+        if (!delayedShow) delay = 0;
+        mBalloonHint.dismiss();
+        if (!mBalloonHint.isShowing()) {
+            mBalloonHint.delayedShow(delay, mHintPositionToInputView);
+        } else {
+            mBalloonHint.delayedUpdate(0, mHintPositionToInputView, -1, -1);
+        }
+    }
+
+    private class PressTimer extends Handler implements Runnable {
+        private boolean mTimerPending = false;
+        private int mPageNoToShow;
+        private int mActiveCandOfPage;
+
+        public PressTimer() {
+            super();
+        }
+
+        public void startTimer(long afterMillis, int pageNo, int activeInPage) {
+            mTimer.removeTimer();
+            postDelayed(this, afterMillis);
+            mTimerPending = true;
+            mPageNoToShow = pageNo;
+            mActiveCandOfPage = activeInPage;
+        }
+
+        public int getPageToShow() {
+            return mPageNoToShow;
+        }
+
+        public int getActiveCandOfPageToShow() {
+            return mActiveCandOfPage;
+        }
+
+        public boolean removeTimer() {
+            if (mTimerPending) {
+                mTimerPending = false;
+                removeCallbacks(this);
+                return true;
+            }
+            return false;
+        }
+
+        public boolean isPending() {
+            return mTimerPending;
+        }
+
+        public void run() {
+            if (mPageNoToShow >= 0 && mActiveCandOfPage >= 0) {
+                // Always enable to highlight the clicked one.
+                showPage(mPageNoToShow, mActiveCandOfPage, true);
+                invalidate();
+            }
+            mTimerPending = false;
+        }
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/CandidateViewListener.java b/PinyinIME/src/com/android/inputmethod/pinyin/CandidateViewListener.java
new file mode 100644
index 0000000..795d119
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/CandidateViewListener.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+/**
+ * Interface to notify the input method when the user clicks a candidate or
+ * makes a direction-gesture on candidate view.
+ */
+public interface CandidateViewListener {
+    public void onClickChoice(int choiceId);
+
+    public void onToLeftGesture();
+
+    public void onToRightGesture();
+
+    public void onToTopGesture();
+
+    public void onToBottomGesture();
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/CandidatesContainer.java b/PinyinIME/src/com/android/inputmethod/pinyin/CandidatesContainer.java
new file mode 100644
index 0000000..5b2a999
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/CandidatesContainer.java
@@ -0,0 +1,474 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import com.android.inputmethod.pinyin.PinyinIME.DecodingInfo;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.TranslateAnimation;
+import android.view.animation.Animation.AnimationListener;
+import android.widget.ImageButton;
+import android.widget.RelativeLayout;
+import android.widget.ViewFlipper;
+
+interface ArrowUpdater {
+    void updateArrowStatus();
+}
+
+
+/**
+ * Container used to host the two candidate views. When user drags on candidate
+ * view, animation is used to dismiss the current candidate view and show a new
+ * one. These two candidate views and their parent are hosted by this container.
+ * <p>
+ * Besides the candidate views, there are two arrow views to show the page
+ * forward/backward arrows.
+ * </p>
+ */
+public class CandidatesContainer extends RelativeLayout implements
+        OnTouchListener, AnimationListener, ArrowUpdater {
+    /**
+     * Alpha value to show an enabled arrow.
+     */
+    private static int ARROW_ALPHA_ENABLED = 0xff;
+
+    /**
+     * Alpha value to show an disabled arrow.
+     */
+    private static int ARROW_ALPHA_DISABLED = 0x40;
+
+    /**
+     * Animation time to show a new candidate view and dismiss the old one.
+     */
+    private static int ANIMATION_TIME = 200;
+
+    /**
+     * Listener used to notify IME that user clicks a candidate, or navigate
+     * between them.
+     */
+    private CandidateViewListener mCvListener;
+
+    /**
+     * The left arrow button used to show previous page.
+     */
+    private ImageButton mLeftArrowBtn;
+
+    /**
+     * The right arrow button used to show next page.
+     */
+    private ImageButton mRightArrowBtn;
+
+    /**
+     * Decoding result to show.
+     */
+    private DecodingInfo mDecInfo;
+
+    /**
+     * The animation view used to show candidates. It contains two views.
+     * Normally, the candidates are shown one of them. When user navigates to
+     * another page, animation effect will be performed.
+     */
+    private ViewFlipper mFlipper;
+
+    /**
+     * The x offset of the flipper in this container.
+     */
+    private int xOffsetForFlipper;
+
+    /**
+     * Animation used by the incoming view when the user navigates to a left
+     * page.
+     */
+    private Animation mInAnimPushLeft;
+
+    /**
+     * Animation used by the incoming view when the user navigates to a right
+     * page.
+     */
+    private Animation mInAnimPushRight;
+
+    /**
+     * Animation used by the incoming view when the user navigates to a page
+     * above. If the page navigation is triggered by DOWN key, this animation is
+     * used.
+     */
+    private Animation mInAnimPushUp;
+
+    /**
+     * Animation used by the incoming view when the user navigates to a page
+     * below. If the page navigation is triggered by UP key, this animation is
+     * used.
+     */
+    private Animation mInAnimPushDown;
+
+    /**
+     * Animation used by the outgoing view when the user navigates to a left
+     * page.
+     */
+    private Animation mOutAnimPushLeft;
+
+    /**
+     * Animation used by the outgoing view when the user navigates to a right
+     * page.
+     */
+    private Animation mOutAnimPushRight;
+
+    /**
+     * Animation used by the outgoing view when the user navigates to a page
+     * above. If the page navigation is triggered by DOWN key, this animation is
+     * used.
+     */
+    private Animation mOutAnimPushUp;
+
+    /**
+     * Animation used by the incoming view when the user navigates to a page
+     * below. If the page navigation is triggered by UP key, this animation is
+     * used.
+     */
+    private Animation mOutAnimPushDown;
+
+    /**
+     * Animation object which is used for the incoming view currently.
+     */
+    private Animation mInAnimInUse;
+
+    /**
+     * Animation object which is used for the outgoing view currently.
+     */
+    private Animation mOutAnimInUse;
+
+    /**
+     * Current page number in display.
+     */
+    private int mCurrentPage = -1;
+
+    public CandidatesContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void initialize(CandidateViewListener cvListener,
+            BalloonHint balloonHint, GestureDetector gestureDetector) {
+        mCvListener = cvListener;
+
+        mLeftArrowBtn = (ImageButton) findViewById(R.id.arrow_left_btn);
+        mRightArrowBtn = (ImageButton) findViewById(R.id.arrow_right_btn);
+        mLeftArrowBtn.setOnTouchListener(this);
+        mRightArrowBtn.setOnTouchListener(this);
+
+        mFlipper = (ViewFlipper) findViewById(R.id.candidate_flipper);
+        mFlipper.setMeasureAllChildren(true);
+
+        invalidate();
+        requestLayout();
+
+        for (int i = 0; i < mFlipper.getChildCount(); i++) {
+            CandidateView cv = (CandidateView) mFlipper.getChildAt(i);
+            cv.initialize(this, balloonHint, gestureDetector, mCvListener);
+        }
+    }
+
+    public void showCandidates(PinyinIME.DecodingInfo decInfo,
+            boolean enableActiveHighlight) {
+        if (null == decInfo) return;
+        mDecInfo = decInfo;
+        mCurrentPage = 0;
+
+        if (decInfo.isCandidatesListEmpty()) {
+            showArrow(mLeftArrowBtn, false);
+            showArrow(mRightArrowBtn, false);
+        } else {
+            showArrow(mLeftArrowBtn, true);
+            showArrow(mRightArrowBtn, true);
+        }
+
+        for (int i = 0; i < mFlipper.getChildCount(); i++) {
+            CandidateView cv = (CandidateView) mFlipper.getChildAt(i);
+            cv.setDecodingInfo(mDecInfo);
+        }
+        stopAnimation();
+
+        CandidateView cv = (CandidateView) mFlipper.getCurrentView();
+        cv.showPage(mCurrentPage, 0, enableActiveHighlight);
+
+        updateArrowStatus();
+        invalidate();
+    }
+
+    public int getCurrentPage() {
+        return mCurrentPage;
+    }
+
+    public void enableActiveHighlight(boolean enableActiveHighlight) {
+        CandidateView cv = (CandidateView) mFlipper.getCurrentView();
+        cv.enableActiveHighlight(enableActiveHighlight);
+        invalidate();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Environment env = Environment.getInstance();
+        int measuredWidth = env.getScreenWidth();
+        int measuredHeight = getPaddingTop();
+        measuredHeight += env.getHeightForCandidates();
+        widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth,
+                MeasureSpec.EXACTLY);
+        heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight,
+                MeasureSpec.EXACTLY);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (null != mLeftArrowBtn) {
+            xOffsetForFlipper = mLeftArrowBtn.getMeasuredWidth();
+        }
+    }
+
+    public boolean activeCurseBackward() {
+        if (mFlipper.isFlipping() || null == mDecInfo) {
+            return false;
+        }
+
+        CandidateView cv = (CandidateView) mFlipper.getCurrentView();
+
+        if (cv.activeCurseBackward()) {
+            cv.invalidate();
+            return true;
+        } else {
+            return pageBackward(true, true);
+        }
+    }
+
+    public boolean activeCurseForward() {
+        if (mFlipper.isFlipping() || null == mDecInfo) {
+            return false;
+        }
+
+        CandidateView cv = (CandidateView) mFlipper.getCurrentView();
+
+        if (cv.activeCursorForward()) {
+            cv.invalidate();
+            return true;
+        } else {
+            return pageForward(true, true);
+        }
+    }
+
+    public boolean pageBackward(boolean animLeftRight,
+            boolean enableActiveHighlight) {
+        if (null == mDecInfo) return false;
+
+        if (mFlipper.isFlipping() || 0 == mCurrentPage) return false;
+
+        int child = mFlipper.getDisplayedChild();
+        int childNext = (child + 1) % 2;
+        CandidateView cv = (CandidateView) mFlipper.getChildAt(child);
+        CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext);
+
+        mCurrentPage--;
+        int activeCandInPage = cv.getActiveCandiatePosInPage();
+        if (animLeftRight)
+            activeCandInPage = mDecInfo.mPageStart.elementAt(mCurrentPage + 1)
+                    - mDecInfo.mPageStart.elementAt(mCurrentPage) - 1;
+
+        cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight);
+        loadAnimation(animLeftRight, false);
+        startAnimation();
+
+        updateArrowStatus();
+        return true;
+    }
+
+    public boolean pageForward(boolean animLeftRight,
+            boolean enableActiveHighlight) {
+        if (null == mDecInfo) return false;
+
+        if (mFlipper.isFlipping() || !mDecInfo.preparePage(mCurrentPage + 1)) {
+            return false;
+        }
+
+        int child = mFlipper.getDisplayedChild();
+        int childNext = (child + 1) % 2;
+        CandidateView cv = (CandidateView) mFlipper.getChildAt(child);
+        int activeCandInPage = cv.getActiveCandiatePosInPage();
+        cv.enableActiveHighlight(enableActiveHighlight);
+
+        CandidateView cvNext = (CandidateView) mFlipper.getChildAt(childNext);
+        mCurrentPage++;
+        if (animLeftRight) activeCandInPage = 0;
+
+        cvNext.showPage(mCurrentPage, activeCandInPage, enableActiveHighlight);
+        loadAnimation(animLeftRight, true);
+        startAnimation();
+
+        updateArrowStatus();
+        return true;
+    }
+
+    public int getActiveCandiatePos() {
+        if (null == mDecInfo) return -1;
+        CandidateView cv = (CandidateView) mFlipper.getCurrentView();
+        return cv.getActiveCandiatePosGlobal();
+    }
+
+    public void updateArrowStatus() {
+        if (mCurrentPage < 0) return;
+        boolean forwardEnabled = mDecInfo.pageForwardable(mCurrentPage);
+        boolean backwardEnabled = mDecInfo.pageBackwardable(mCurrentPage);
+
+        if (backwardEnabled) {
+            enableArrow(mLeftArrowBtn, true);
+        } else {
+            enableArrow(mLeftArrowBtn, false);
+        }
+        if (forwardEnabled) {
+            enableArrow(mRightArrowBtn, true);
+        } else {
+            enableArrow(mRightArrowBtn, false);
+        }
+    }
+
+    private void enableArrow(ImageButton arrowBtn, boolean enabled) {
+        arrowBtn.setEnabled(enabled);
+        if (enabled)
+            arrowBtn.setAlpha(ARROW_ALPHA_ENABLED);
+        else
+            arrowBtn.setAlpha(ARROW_ALPHA_DISABLED);
+    }
+
+    private void showArrow(ImageButton arrowBtn, boolean show) {
+        if (show)
+            arrowBtn.setVisibility(View.VISIBLE);
+        else
+            arrowBtn.setVisibility(View.INVISIBLE);
+    }
+
+    public boolean onTouch(View v, MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            if (v == mLeftArrowBtn) {
+                mCvListener.onToRightGesture();
+            } else if (v == mRightArrowBtn) {
+                mCvListener.onToLeftGesture();
+            }
+        } else if (event.getAction() == MotionEvent.ACTION_UP) {
+            CandidateView cv = (CandidateView) mFlipper.getCurrentView();
+            cv.enableActiveHighlight(true);
+        }
+
+        return false;
+    }
+
+    // The reason why we handle candiate view's touch events here is because
+    // that the view under the focused view may get touch events instead of the
+    // focused one.
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        event.offsetLocation(-xOffsetForFlipper, 0);
+        CandidateView cv = (CandidateView) mFlipper.getCurrentView();
+        cv.onTouchEventReal(event);
+        return true;
+    }
+
+    public void loadAnimation(boolean animLeftRight, boolean forward) {
+        if (animLeftRight) {
+            if (forward) {
+                if (null == mInAnimPushLeft) {
+                    mInAnimPushLeft = createAnimation(1.0f, 0, 0, 0, 0, 1.0f,
+                            ANIMATION_TIME);
+                    mOutAnimPushLeft = createAnimation(0, -1.0f, 0, 0, 1.0f, 0,
+                            ANIMATION_TIME);
+                }
+                mInAnimInUse = mInAnimPushLeft;
+                mOutAnimInUse = mOutAnimPushLeft;
+            } else {
+                if (null == mInAnimPushRight) {
+                    mInAnimPushRight = createAnimation(-1.0f, 0, 0, 0, 0, 1.0f,
+                            ANIMATION_TIME);
+                    mOutAnimPushRight = createAnimation(0, 1.0f, 0, 0, 1.0f, 0,
+                            ANIMATION_TIME);
+                }
+                mInAnimInUse = mInAnimPushRight;
+                mOutAnimInUse = mOutAnimPushRight;
+            }
+        } else {
+            if (forward) {
+                if (null == mInAnimPushUp) {
+                    mInAnimPushUp = createAnimation(0, 0, 1.0f, 0, 0, 1.0f,
+                            ANIMATION_TIME);
+                    mOutAnimPushUp = createAnimation(0, 0, 0, -1.0f, 1.0f, 0,
+                            ANIMATION_TIME);
+                }
+                mInAnimInUse = mInAnimPushUp;
+                mOutAnimInUse = mOutAnimPushUp;
+            } else {
+                if (null == mInAnimPushDown) {
+                    mInAnimPushDown = createAnimation(0, 0, -1.0f, 0, 0, 1.0f,
+                            ANIMATION_TIME);
+                    mOutAnimPushDown = createAnimation(0, 0, 0, 1.0f, 1.0f, 0,
+                            ANIMATION_TIME);
+                }
+                mInAnimInUse = mInAnimPushDown;
+                mOutAnimInUse = mOutAnimPushDown;
+            }
+        }
+
+        mInAnimInUse.setAnimationListener(this);
+
+        mFlipper.setInAnimation(mInAnimInUse);
+        mFlipper.setOutAnimation(mOutAnimInUse);
+    }
+
+    private Animation createAnimation(float xFrom, float xTo, float yFrom,
+            float yTo, float alphaFrom, float alphaTo, long duration) {
+        AnimationSet animSet = new AnimationSet(getContext(), null);
+        Animation trans = new TranslateAnimation(Animation.RELATIVE_TO_SELF,
+                xFrom, Animation.RELATIVE_TO_SELF, xTo,
+                Animation.RELATIVE_TO_SELF, yFrom, Animation.RELATIVE_TO_SELF,
+                yTo);
+        Animation alpha = new AlphaAnimation(alphaFrom, alphaTo);
+        animSet.addAnimation(trans);
+        animSet.addAnimation(alpha);
+        animSet.setDuration(duration);
+        return animSet;
+    }
+
+    private void startAnimation() {
+        mFlipper.showNext();
+    }
+
+    private void stopAnimation() {
+        mFlipper.stopFlipping();
+    }
+
+    public void onAnimationEnd(Animation animation) {
+        if (!mLeftArrowBtn.isPressed() && !mRightArrowBtn.isPressed()) {
+            CandidateView cv = (CandidateView) mFlipper.getCurrentView();
+            cv.enableActiveHighlight(true);
+        }
+    }
+
+    public void onAnimationRepeat(Animation animation) {
+    }
+
+    public void onAnimationStart(Animation animation) {
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/ComposingView.java b/PinyinIME/src/com/android/inputmethod/pinyin/ComposingView.java
new file mode 100644
index 0000000..b781cda
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/ComposingView.java
@@ -0,0 +1,279 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+
+/**
+ * View used to show composing string (The Pinyin string for the unselected
+ * syllables and the Chinese string for the selected syllables.)
+ */
+public class ComposingView extends View {
+    /**
+     * <p>
+     * There are three statuses for the composing view.
+     * </p>
+     * 
+     * <p>
+     * {@link #SHOW_PINYIN} is used to show the current Pinyin string without
+     * highlighted effect. When user inputs Pinyin characters one by one, the
+     * Pinyin string will be shown in this mode.
+     * </p>
+     * <p>
+     * {@link #SHOW_STRING_LOWERCASE} is used to show the Pinyin string in
+     * lowercase with highlighted effect. When user presses UP key and there is
+     * no fixed Chinese characters, composing view will switch from
+     * {@link #SHOW_PINYIN} to this mode, and in this mode, user can press
+     * confirm key to input the lower-case string, so that user can input
+     * English letter in Chinese mode.
+     * </p>
+     * <p>
+     * {@link #EDIT_PINYIN} is used to edit the Pinyin string (shown with
+     * highlighted effect). When current status is {@link #SHOW_PINYIN} and user
+     * presses UP key, if there are fixed Characters, the input method will
+     * switch to {@link #EDIT_PINYIN} thus user can modify some characters in
+     * the middle of the Pinyin string. If the current status is
+     * {@link #SHOW_STRING_LOWERCASE} and user presses LEFT and RIGHT key, it
+     * will also switch to {@link #EDIT_PINYIN}.
+     * </p>
+     * <p>
+     * Whenever user presses down key, the status switches to
+     * {@link #SHOW_PINYIN}.
+     * </p>
+     * <p>
+     * When composing view's status is {@link #SHOW_PINYIN}, the IME's status is
+     * {@link PinyinIME.ImeState#STATE_INPUT}, otherwise, the IME's status
+     * should be {@link PinyinIME.ImeState#STATE_COMPOSING}.
+     * </p>
+     */
+    public enum ComposingStatus {
+        SHOW_PINYIN, SHOW_STRING_LOWERCASE, EDIT_PINYIN,
+    }
+
+    private static final int LEFT_RIGHT_MARGIN = 5;
+
+    /**
+     * Used to draw composing string. When drawing the active and idle part of
+     * the spelling(Pinyin) string, the color may be changed.
+     */
+    private Paint mPaint;
+
+    /**
+     * Drawable used to draw highlight effect.
+     */
+    private Drawable mHlDrawable;
+
+    /**
+     * Drawable used to draw cursor for editing mode.
+     */
+    private Drawable mCursor;
+
+    /**
+     * Used to estimate dimensions to show the string .
+     */
+    private FontMetricsInt mFmi;
+
+    private int mStrColor;
+    private int mStrColorHl;
+    private int mStrColorIdle;
+
+    private int mFontSize;
+
+    private ComposingStatus mComposingStatus;
+
+    PinyinIME.DecodingInfo mDecInfo;
+
+    public ComposingView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        Resources r = context.getResources();
+        mHlDrawable = r.getDrawable(R.drawable.composing_hl_bg);
+        mCursor = r.getDrawable(R.drawable.composing_area_cursor);
+
+        mStrColor = r.getColor(R.color.composing_color);
+        mStrColorHl = r.getColor(R.color.composing_color_hl);
+        mStrColorIdle = r.getColor(R.color.composing_color_idle);
+
+        mFontSize = r.getDimensionPixelSize(R.dimen.composing_height);
+
+        mPaint = new Paint();
+        mPaint.setColor(mStrColor);
+        mPaint.setAntiAlias(true);
+        mPaint.setTextSize(mFontSize);
+
+        mFmi = mPaint.getFontMetricsInt();
+    }
+
+    public void reset() {
+        mComposingStatus = ComposingStatus.SHOW_PINYIN;
+    }
+
+    /**
+     * Set the composing string to show. If the IME status is
+     * {@link PinyinIME.ImeState#STATE_INPUT}, the composing view's status will
+     * be set to {@link ComposingStatus#SHOW_PINYIN}, otherwise the composing
+     * view will set its status to {@link ComposingStatus#SHOW_STRING_LOWERCASE}
+     * or {@link ComposingStatus#EDIT_PINYIN} automatically.
+     */
+    public void setDecodingInfo(PinyinIME.DecodingInfo decInfo,
+            PinyinIME.ImeState imeStatus) {
+        mDecInfo = decInfo;
+
+        if (PinyinIME.ImeState.STATE_INPUT == imeStatus) {
+            mComposingStatus = ComposingStatus.SHOW_PINYIN;
+            mDecInfo.moveCursorToEdge(false);
+        } else {
+            if (decInfo.getFixedLen() != 0
+                    || ComposingStatus.EDIT_PINYIN == mComposingStatus) {
+                mComposingStatus = ComposingStatus.EDIT_PINYIN;
+            } else {
+                mComposingStatus = ComposingStatus.SHOW_STRING_LOWERCASE;
+            }
+            mDecInfo.moveCursor(0);
+        }
+
+        measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        requestLayout();
+        invalidate();
+    }
+
+    public boolean moveCursor(int keyCode) {
+        if (keyCode != KeyEvent.KEYCODE_DPAD_LEFT
+                && keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) return false;
+
+        if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {
+            int offset = 0;
+            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT)
+                offset = -1;
+            else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) offset = 1;
+            mDecInfo.moveCursor(offset);
+        } else if (ComposingStatus.SHOW_STRING_LOWERCASE == mComposingStatus) {
+            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                    || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+                mComposingStatus = ComposingStatus.EDIT_PINYIN;
+
+                measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+                requestLayout();
+            }
+
+        }
+        invalidate();
+        return true;
+    }
+
+    public ComposingStatus getComposingStatus() {
+        return mComposingStatus;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        float width;
+        int height;
+        height = mFmi.bottom - mFmi.top + mPaddingTop + mPaddingBottom;
+
+        if (null == mDecInfo) {
+            width = 0;
+        } else {
+            width = mPaddingLeft + mPaddingRight + LEFT_RIGHT_MARGIN * 2;
+
+            String str;
+            if (ComposingStatus.SHOW_STRING_LOWERCASE == mComposingStatus) {
+                str = mDecInfo.getOrigianlSplStr().toString();
+            } else {
+                str = mDecInfo.getComposingStrForDisplay();
+            }
+            width += mPaint.measureText(str, 0, str.length());
+        }
+        setMeasuredDimension((int) (width + 0.5f), height);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (ComposingStatus.EDIT_PINYIN == mComposingStatus
+                || ComposingStatus.SHOW_PINYIN == mComposingStatus) {
+            drawForPinyin(canvas);
+            return;
+        }
+
+        float x, y;
+        x = mPaddingLeft + LEFT_RIGHT_MARGIN;
+        y = -mFmi.top + mPaddingTop;
+
+        mPaint.setColor(mStrColorHl);
+        mHlDrawable.setBounds(mPaddingLeft, mPaddingTop, getWidth()
+                - mPaddingRight, getHeight() - mPaddingBottom);
+        mHlDrawable.draw(canvas);
+
+        String splStr = mDecInfo.getOrigianlSplStr().toString();
+        canvas.drawText(splStr, 0, splStr.length(), x, y, mPaint);
+    }
+
+    private void drawCursor(Canvas canvas, float x) {
+        mCursor.setBounds((int) x, mPaddingTop, (int) x
+                + mCursor.getIntrinsicWidth(), getHeight() - mPaddingBottom);
+        mCursor.draw(canvas);
+    }
+
+    private void drawForPinyin(Canvas canvas) {
+        float x, y;
+        x = mPaddingLeft + LEFT_RIGHT_MARGIN;
+        y = -mFmi.top + mPaddingTop;
+
+        mPaint.setColor(mStrColor);
+
+        int cursorPos = mDecInfo.getCursorPosInCmpsDisplay();
+        int cmpsPos = cursorPos;
+        String cmpsStr = mDecInfo.getComposingStrForDisplay();
+        int activeCmpsLen = mDecInfo.getActiveCmpsDisplayLen();
+        if (cursorPos > activeCmpsLen) cmpsPos = activeCmpsLen;
+        canvas.drawText(cmpsStr, 0, cmpsPos, x, y, mPaint);
+        x += mPaint.measureText(cmpsStr, 0, cmpsPos);
+        if (cursorPos <= activeCmpsLen) {
+            if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {
+                drawCursor(canvas, x);
+            }
+            canvas.drawText(cmpsStr, cmpsPos, activeCmpsLen, x, y, mPaint);
+        }
+
+        x += mPaint.measureText(cmpsStr, cmpsPos, activeCmpsLen);
+
+        if (cmpsStr.length() > activeCmpsLen) {
+            mPaint.setColor(mStrColorIdle);
+            int oriPos = activeCmpsLen;
+            if (cursorPos > activeCmpsLen) {
+                canvas.drawText(cmpsStr, oriPos, cursorPos, x, y, mPaint);
+                x += mPaint.measureText(cmpsStr, oriPos, cursorPos);
+
+                if (ComposingStatus.EDIT_PINYIN == mComposingStatus) {
+                    drawCursor(canvas, x);
+                }
+
+                oriPos = cursorPos;
+            }
+            canvas.drawText(cmpsStr, oriPos, cmpsStr.length(), x, y, mPaint);
+        }
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/EnglishInputProcessor.java b/PinyinIME/src/com/android/inputmethod/pinyin/EnglishInputProcessor.java
new file mode 100644
index 0000000..6d61119
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/EnglishInputProcessor.java
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.view.KeyEvent;
+import android.view.inputmethod.InputConnection;
+
+/**
+ * Class to handle English input. 
+ */
+public class EnglishInputProcessor {
+
+    private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+
+    public boolean processKey(InputConnection inputContext, KeyEvent event,
+            boolean upperCase, boolean realAction) {
+        if (null == inputContext || null == event) return false;
+
+        int keyCode = event.getKeyCode();
+
+        CharSequence prefix = null;
+        prefix = inputContext.getTextBeforeCursor(2, 0);
+
+        int keyChar;
+        keyChar = 0;
+        if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
+            keyChar = keyCode - KeyEvent.KEYCODE_A + 'a';
+            if (upperCase) {
+                keyChar = keyChar + 'A' - 'a';
+            }
+        } else if (keyCode >= KeyEvent.KEYCODE_0
+                && keyCode <= KeyEvent.KEYCODE_9)
+            keyChar = keyCode - KeyEvent.KEYCODE_0 + '0';
+        else if (keyCode == KeyEvent.KEYCODE_COMMA)
+            keyChar = ',';
+        else if (keyCode == KeyEvent.KEYCODE_PERIOD)
+            keyChar = '.';
+        else if (keyCode == KeyEvent.KEYCODE_APOSTROPHE)
+            keyChar = '\'';
+        else if (keyCode == KeyEvent.KEYCODE_AT)
+            keyChar = '@';
+        else if (keyCode == KeyEvent.KEYCODE_SLASH) keyChar = '/';
+
+        if (0 == keyChar) {
+            mLastKeyCode = keyCode;
+
+            String insert = null;
+            if (KeyEvent.KEYCODE_DEL == keyCode) {
+                if (realAction)  {
+                    inputContext.deleteSurroundingText(1, 0);
+                }
+            } else if (KeyEvent.KEYCODE_ENTER == keyCode) {
+                insert = "\n";
+            } else if (KeyEvent.KEYCODE_SPACE == keyCode) {
+                insert = " ";
+            } else {
+                return false;
+            }
+
+            if (null != insert && realAction)
+                inputContext.commitText(insert, insert.length());
+
+            return true;
+        }
+
+        if (!realAction)
+            return true;
+
+        if (KeyEvent.KEYCODE_SHIFT_LEFT == mLastKeyCode
+                || KeyEvent.KEYCODE_SHIFT_LEFT == mLastKeyCode) {
+            if (keyChar >= 'a' && keyChar <= 'z')
+                keyChar = keyChar - 'a' + 'A';
+        } else if (KeyEvent.KEYCODE_ALT_LEFT == mLastKeyCode) {
+        }
+
+        String result = String.valueOf((char) keyChar);
+        inputContext.commitText(result, result.length());
+        mLastKeyCode = keyCode;
+        return true;
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/Environment.java b/PinyinIME/src/com/android/inputmethod/pinyin/Environment.java
new file mode 100644
index 0000000..8869294
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/Environment.java
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.Display;
+import android.view.WindowManager;
+
+/**
+ * Global environment configurations for showing soft keyboard and candidate
+ * view. All original dimension values are defined in float, and the real size
+ * is calculated from the float values of and screen size. In this way, this
+ * input method can work even when screen size is changed.
+ */
+public class Environment {
+    /**
+     * The key height for portrait mode. It is relative to the screen height.
+     */
+    private static final float KEY_HEIGHT_RATIO_PORTRAIT = 0.105f;
+
+    /**
+     * The key height for landscape mode. It is relative to the screen height.
+     */
+    private static final float KEY_HEIGHT_RATIO_LANDSCAPE = 0.147f;
+
+    /**
+     * The height of the candidates area for portrait mode. It is relative to
+     * screen height.
+     */
+    private static final float CANDIDATES_AREA_HEIGHT_RATIO_PORTRAIT = 0.084f;
+
+    /**
+     * The height of the candidates area for portrait mode. It is relative to
+     * screen height.
+     */
+    private static final float CANDIDATES_AREA_HEIGHT_RATIO_LANDSCAPE = 0.125f;
+
+    /**
+     * How much should the balloon width be larger than width of the real key.
+     * It is relative to the smaller one of screen width and height.
+     */
+    private static final float KEY_BALLOON_WIDTH_PLUS_RATIO = 0.08f;
+
+    /**
+     * How much should the balloon height be larger than that of the real key.
+     * It is relative to the smaller one of screen width and height.
+     */
+    private static final float KEY_BALLOON_HEIGHT_PLUS_RATIO = 0.07f;
+
+    /**
+     * The text size for normal keys. It is relative to the smaller one of
+     * screen width and height.
+     */
+    private static final float NORMAL_KEY_TEXT_SIZE_RATIO = 0.075f;
+
+    /**
+     * The text size for function keys. It is relative to the smaller one of
+     * screen width and height.
+     */
+    private static final float FUNCTION_KEY_TEXT_SIZE_RATIO = 0.055f;
+
+    /**
+     * The text size balloons of normal keys. It is relative to the smaller one
+     * of screen width and height.
+     */
+    private static final float NORMAL_BALLOON_TEXT_SIZE_RATIO = 0.14f;
+
+    /**
+     * The text size balloons of function keys. It is relative to the smaller
+     * one of screen width and height.
+     */
+    private static final float FUNCTION_BALLOON_TEXT_SIZE_RATIO = 0.085f;
+
+    /**
+     * The configurations are managed in a singleton.
+     */
+    private static Environment mInstance;
+
+    private int mScreenWidth;
+    private int mScreenHeight;
+    private int mKeyHeight;
+    private int mCandidatesAreaHeight;
+    private int mKeyBalloonWidthPlus;
+    private int mKeyBalloonHeightPlus;
+    private int mNormalKeyTextSize;
+    private int mFunctionKeyTextSize;
+    private int mNormalBalloonTextSize;
+    private int mFunctionBalloonTextSize;
+    private Configuration mConfig = new Configuration();
+    private boolean mDebug = false;
+
+    private Environment() {
+    }
+
+    public static Environment getInstance() {
+        if (null == mInstance) {
+            mInstance = new Environment();
+        }
+        return mInstance;
+    }
+
+    public void onConfigurationChanged(Configuration newConfig, Context context) {
+        if (mConfig.orientation != newConfig.orientation) {
+            WindowManager wm = (WindowManager) context
+                    .getSystemService(Context.WINDOW_SERVICE);
+            Display d = wm.getDefaultDisplay();
+            mScreenWidth = d.getWidth();
+            mScreenHeight = d.getHeight();
+
+            int scale;
+            if (mScreenHeight > mScreenWidth) {
+                mKeyHeight = (int) (mScreenHeight * KEY_HEIGHT_RATIO_PORTRAIT);
+                mCandidatesAreaHeight = (int) (mScreenHeight * CANDIDATES_AREA_HEIGHT_RATIO_PORTRAIT);
+                scale = mScreenWidth;
+            } else {
+                mKeyHeight = (int) (mScreenHeight * KEY_HEIGHT_RATIO_LANDSCAPE);
+                mCandidatesAreaHeight = (int) (mScreenHeight * CANDIDATES_AREA_HEIGHT_RATIO_LANDSCAPE);
+                scale = mScreenHeight;
+            }
+            mNormalKeyTextSize = (int) (scale * NORMAL_KEY_TEXT_SIZE_RATIO);
+            mFunctionKeyTextSize = (int) (scale * FUNCTION_KEY_TEXT_SIZE_RATIO);
+            mNormalBalloonTextSize = (int) (scale * NORMAL_BALLOON_TEXT_SIZE_RATIO);
+            mFunctionBalloonTextSize = (int) (scale * FUNCTION_BALLOON_TEXT_SIZE_RATIO);
+            mKeyBalloonWidthPlus = (int) (scale * KEY_BALLOON_WIDTH_PLUS_RATIO);
+            mKeyBalloonHeightPlus = (int) (scale * KEY_BALLOON_HEIGHT_PLUS_RATIO);
+        }
+
+        mConfig.updateFrom(newConfig);
+    }
+
+    public Configuration getConfiguration() {
+        return mConfig;
+    }
+
+    public int getScreenWidth() {
+        return mScreenWidth;
+    }
+
+    public int getScreenHeight() {
+        return mScreenHeight;
+    }
+
+    public int getHeightForCandidates() {
+        return mCandidatesAreaHeight;
+    }
+
+    public float getKeyXMarginFactor() {
+        return 1.0f;
+    }
+
+    public float getKeyYMarginFactor() {
+        if (Configuration.ORIENTATION_LANDSCAPE == mConfig.orientation) {
+            return 0.7f;
+        }
+        return 1.0f;
+    }
+
+    public int getKeyHeight() {
+        return mKeyHeight;
+    }
+
+    public int getKeyBalloonWidthPlus() {
+        return mKeyBalloonWidthPlus;
+    }
+
+    public int getKeyBalloonHeightPlus() {
+        return mKeyBalloonHeightPlus;
+    }
+
+    public int getSkbHeight() {
+        if (Configuration.ORIENTATION_PORTRAIT == mConfig.orientation) {
+            return mKeyHeight * 4;
+        } else if (Configuration.ORIENTATION_LANDSCAPE == mConfig.orientation) {
+            return mKeyHeight * 4;
+        }
+        return 0;
+    }
+
+    public int getKeyTextSize(boolean isFunctionKey) {
+        if (isFunctionKey) {
+            return mFunctionKeyTextSize;
+        } else {
+            return mNormalKeyTextSize;
+        }
+    }
+
+    public int getBalloonTextSize(boolean isFunctionKey) {
+        if (isFunctionKey) {
+            return mFunctionBalloonTextSize;
+        } else {
+            return mNormalBalloonTextSize;
+        }
+    }
+
+    public boolean hasHardKeyboard() {
+        if (mConfig.keyboard == Configuration.KEYBOARD_NOKEYS
+                || mConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
+            return false;
+        }
+        return true;
+    }
+
+    public boolean needDebug() {
+        return mDebug;
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/InputModeSwitcher.java b/PinyinIME/src/com/android/inputmethod/pinyin/InputModeSwitcher.java
new file mode 100644
index 0000000..96b59c3
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/InputModeSwitcher.java
@@ -0,0 +1,821 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import com.android.inputmethod.pinyin.SoftKeyboard.KeyRow;
+
+import android.content.res.Resources;
+import android.view.inputmethod.EditorInfo;
+
+/**
+ * Switcher used to switching input mode between Chinese, English, symbol,etc.
+ */
+public class InputModeSwitcher {
+    /**
+     * User defined key code, used by soft keyboard.
+     */
+    private static final int USERDEF_KEYCODE_SHIFT_1 = -1;
+
+    /**
+     * User defined key code, used by soft keyboard.
+     */
+    private static final int USERDEF_KEYCODE_LANG_2 = -2;
+
+    /**
+     * User defined key code, used by soft keyboard.
+     */
+    private static final int USERDEF_KEYCODE_SYM_3 = -3;
+
+    /**
+     * User defined key code, used by soft keyboard.
+     */
+    public static final int USERDEF_KEYCODE_PHONE_SYM_4 = -4;
+
+    /**
+     * User defined key code, used by soft keyboard.
+     */
+    private static final int USERDEF_KEYCODE_MORE_SYM_5 = -5;
+
+    /**
+     * User defined key code, used by soft keyboard.
+     */
+    private static final int USERDEF_KEYCODE_SMILEY_6 = -6;
+
+
+    /**
+     * Bits used to indicate soft keyboard layout. If none bit is set, the
+     * current input mode does not require a soft keyboard.
+     **/
+    private static final int MASK_SKB_LAYOUT = 0xf0000000;
+
+    /**
+     * A kind of soft keyboard layout. An input mode should be anded with
+     * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout.
+     */
+    private static final int MASK_SKB_LAYOUT_QWERTY = 0x10000000;
+
+    /**
+     * A kind of soft keyboard layout. An input mode should be anded with
+     * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout.
+     */
+    private static final int MASK_SKB_LAYOUT_SYMBOL1 = 0x20000000;
+
+    /**
+     * A kind of soft keyboard layout. An input mode should be anded with
+     * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout.
+     */
+    private static final int MASK_SKB_LAYOUT_SYMBOL2 = 0x30000000;
+
+    /**
+     * A kind of soft keyboard layout. An input mode should be anded with
+     * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout.
+     */
+    private static final int MASK_SKB_LAYOUT_SMILEY = 0x40000000;
+
+    /**
+     * A kind of soft keyboard layout. An input mode should be anded with
+     * {@link #MASK_SKB_LAYOUT} to get its soft keyboard layout.
+     */
+    private static final int MASK_SKB_LAYOUT_PHONE = 0x50000000;
+
+    /**
+     * Used to indicate which language the current input mode is in. If the
+     * current input mode works with a none-QWERTY soft keyboard, these bits are
+     * also used to get language information. For example, a Chinese symbol soft
+     * keyboard and an English one are different in an icon which is used to
+     * tell user the language information. BTW, the smiley soft keyboard mode
+     * should be set with {@link #MASK_LANGUAGE_CN} because it can only be
+     * launched from Chinese QWERTY soft keyboard, and it has Chinese icon on
+     * soft keyboard.
+     */
+    private static final int MASK_LANGUAGE = 0x0f000000;
+
+    /**
+     * Used to indicate the current language. An input mode should be anded with
+     * {@link #MASK_LANGUAGE} to get this information.
+     */
+    private static final int MASK_LANGUAGE_CN = 0x01000000;
+
+    /**
+     * Used to indicate the current language. An input mode should be anded with
+     * {@link #MASK_LANGUAGE} to get this information.
+     */
+    private static final int MASK_LANGUAGE_EN = 0x02000000;
+
+    /**
+     * Used to indicate which case the current input mode is in. For example,
+     * English QWERTY has lowercase and uppercase. For the Chinese QWERTY, these
+     * bits are ignored. For phone keyboard layout, these bits can be
+     * {@link #MASK_CASE_UPPER} to request symbol page for phone soft keyboard.
+     */
+    private static final int MASK_CASE = 0x00f00000;
+
+    /**
+     * Used to indicate the current case information. An input mode should be
+     * anded with {@link #MASK_CASE} to get this information.
+     */
+    private static final int MASK_CASE_LOWER = 0x00100000;
+
+    /**
+     * Used to indicate the current case information. An input mode should be
+     * anded with {@link #MASK_CASE} to get this information.
+     */
+    private static final int MASK_CASE_UPPER = 0x00200000;
+
+    /**
+     * Mode for inputing Chinese with soft keyboard.
+     */
+    public static final int MODE_SKB_CHINESE = (MASK_SKB_LAYOUT_QWERTY | MASK_LANGUAGE_CN);
+
+    /**
+     * Mode for inputing basic symbols for Chinese mode with soft keyboard.
+     */
+    public static final int MODE_SKB_SYMBOL1_CN = (MASK_SKB_LAYOUT_SYMBOL1 | MASK_LANGUAGE_CN);
+
+    /**
+     * Mode for inputing more symbols for Chinese mode with soft keyboard.
+     */
+    public static final int MODE_SKB_SYMBOL2_CN = (MASK_SKB_LAYOUT_SYMBOL2 | MASK_LANGUAGE_CN);
+
+    /**
+     * Mode for inputing English lower characters with soft keyboard.
+     */
+    public static final int MODE_SKB_ENGLISH_LOWER = (MASK_SKB_LAYOUT_QWERTY
+            | MASK_LANGUAGE_EN | MASK_CASE_LOWER);
+
+    /**
+     * Mode for inputing English upper characters with soft keyboard.
+     */
+    public static final int MODE_SKB_ENGLISH_UPPER = (MASK_SKB_LAYOUT_QWERTY
+            | MASK_LANGUAGE_EN | MASK_CASE_UPPER);
+
+    /**
+     * Mode for inputing basic symbols for English mode with soft keyboard.
+     */
+    public static final int MODE_SKB_SYMBOL1_EN = (MASK_SKB_LAYOUT_SYMBOL1 | MASK_LANGUAGE_EN);
+
+    /**
+     * Mode for inputing more symbols for English mode with soft keyboard.
+     */
+    public static final int MODE_SKB_SYMBOL2_EN = (MASK_SKB_LAYOUT_SYMBOL2 | MASK_LANGUAGE_EN);
+
+    /**
+     * Mode for inputing smileys with soft keyboard.
+     */
+    public static final int MODE_SKB_SMILEY = (MASK_SKB_LAYOUT_SMILEY | MASK_LANGUAGE_CN);
+
+    /**
+     * Mode for inputing phone numbers.
+     */
+    public static final int MODE_SKB_PHONE_NUM = (MASK_SKB_LAYOUT_PHONE);
+
+    /**
+     * Mode for inputing phone numbers.
+     */
+    public static final int MODE_SKB_PHONE_SYM = (MASK_SKB_LAYOUT_PHONE | MASK_CASE_UPPER);
+
+    /**
+     * Mode for inputing Chinese with a hardware keyboard.
+     */
+    public static final int MODE_HKB_CHINESE = (MASK_LANGUAGE_CN);
+
+    /**
+     * Mode for inputing English with a hardware keyboard
+     */
+    public static final int MODE_HKB_ENGLISH = (MASK_LANGUAGE_EN);
+
+    /**
+     * Unset mode.
+     */
+    public static final int MODE_UNSET = 0;
+
+    /**
+     * Maximum toggle states for a soft keyboard.
+     */
+    public static final int MAX_TOGGLE_STATES = 4;
+
+    /**
+     * The input mode for the current edit box.
+     */
+    private int mInputMode = MODE_UNSET;
+
+    /**
+     * Used to remember previous input mode. When user enters an edit field, the
+     * previous input mode will be tried. If the previous mode can not be used
+     * for the current situation (For example, previous mode is a soft keyboard
+     * mode to input symbols, and we have a hardware keyboard for the current
+     * situation), {@link #mRecentLauageInputMode} will be tried.
+     **/
+    private int mPreviousInputMode = MODE_SKB_CHINESE;
+
+    /**
+     * Used to remember recent mode to input language.
+     */
+    private int mRecentLauageInputMode = MODE_SKB_CHINESE;
+
+    /**
+     * Editor information of the current edit box.
+     */
+    private EditorInfo mEditorInfo;
+
+    /**
+     * Used to indicate required toggling operations.
+     */
+    private ToggleStates mToggleStates = new ToggleStates();
+
+    /**
+     * The current field is a short message field?
+     */
+    private boolean mShortMessageField;
+
+    /**
+     * Is return key in normal state?
+     */
+    private boolean mEnterKeyNormal = true;
+
+    /**
+     * Current icon. 0 for none icon.
+     */
+    int mInputIcon = R.drawable.ime_pinyin;
+
+    /**
+     * IME service.
+     */
+    private PinyinIME mImeService;
+
+    /**
+     * Key toggling state for Chinese mode.
+     */
+    private int mToggleStateCn;
+
+    /**
+     * Key toggling state for Chinese mode with candidates.
+     */
+    private int mToggleStateCnCand;
+
+    /**
+     * Key toggling state for English lowwercase mode.
+     */
+    private int mToggleStateEnLower;
+
+    /**
+     * Key toggling state for English upppercase mode.
+     */
+    private int mToggleStateEnUpper;
+
+    /**
+     * Key toggling state for English symbol mode for the first page.
+     */
+    private int mToggleStateEnSym1;
+
+    /**
+     * Key toggling state for English symbol mode for the second page.
+     */
+    private int mToggleStateEnSym2;
+
+    /**
+     * Key toggling state for smiley mode.
+     */
+    private int mToggleStateSmiley;
+
+    /**
+     * Key toggling state for phone symbol mode.
+     */
+    private int mToggleStatePhoneSym;
+
+    /**
+     * Key toggling state for GO action of ENTER key.
+     */
+    private int mToggleStateGo;
+
+    /**
+     * Key toggling state for SEARCH action of ENTER key.
+     */
+    private int mToggleStateSearch;
+
+    /**
+     * Key toggling state for SEND action of ENTER key.
+     */
+    private int mToggleStateSend;
+
+    /**
+     * Key toggling state for NEXT action of ENTER key.
+     */
+    private int mToggleStateNext;
+
+    /**
+     * Key toggling state for SEND action of ENTER key.
+     */
+    private int mToggleStateDone;
+
+    /**
+     * QWERTY row toggling state for Chinese input.
+     */
+    private int mToggleRowCn;
+
+    /**
+     * QWERTY row toggling state for English input.
+     */
+    private int mToggleRowEn;
+
+    /**
+     * QWERTY row toggling state for URI input.
+     */
+    private int mToggleRowUri;
+
+    /**
+     * QWERTY row toggling state for email address input.
+     */
+    private int mToggleRowEmailAddress;
+
+    class ToggleStates {
+        /**
+         * If it is true, this soft keyboard is a QWERTY one.
+         */
+        boolean mQwerty;
+
+        /**
+         * If {@link #mQwerty} is true, this variable is used to decide the
+         * letter case of the QWERTY keyboard.
+         */
+        boolean mQwertyUpperCase;
+
+        /**
+         * The id of enabled row in the soft keyboard. Refer to
+         * {@link com.android.inputmethod.pinyin.SoftKeyboard.KeyRow} for
+         * details.
+         */
+        public int mRowIdToEnable;
+
+        /**
+         * Used to store all other toggle states for the current input mode.
+         */
+        public int mKeyStates[] = new int[MAX_TOGGLE_STATES];
+
+        /**
+         * Number of states to toggle.
+         */
+        public int mKeyStatesNum;
+    }
+
+    public InputModeSwitcher(PinyinIME imeService) {
+        mImeService = imeService;
+        Resources r = mImeService.getResources();
+        mToggleStateCn = Integer.parseInt(r.getString(R.string.toggle_cn));
+        mToggleStateCnCand = Integer.parseInt(r
+                .getString(R.string.toggle_cn_cand));
+        mToggleStateEnLower = Integer.parseInt(r
+                .getString(R.string.toggle_en_lower));
+        mToggleStateEnUpper = Integer.parseInt(r
+                .getString(R.string.toggle_en_upper));
+        mToggleStateEnSym1 = Integer.parseInt(r
+                .getString(R.string.toggle_en_sym1));
+        mToggleStateEnSym2 = Integer.parseInt(r
+                .getString(R.string.toggle_en_sym2));
+        mToggleStateSmiley = Integer.parseInt(r
+                .getString(R.string.toggle_smiley));
+        mToggleStatePhoneSym = Integer.parseInt(r
+                .getString(R.string.toggle_phone_sym));
+
+        mToggleStateGo = Integer
+                .parseInt(r.getString(R.string.toggle_enter_go));
+        mToggleStateSearch = Integer.parseInt(r
+                .getString(R.string.toggle_enter_search));
+        mToggleStateSend = Integer.parseInt(r
+                .getString(R.string.toggle_enter_send));
+        mToggleStateNext = Integer.parseInt(r
+                .getString(R.string.toggle_enter_next));
+        mToggleStateDone = Integer.parseInt(r
+                .getString(R.string.toggle_enter_done));
+
+        mToggleRowCn = Integer.parseInt(r.getString(R.string.toggle_row_cn));
+        mToggleRowEn = Integer.parseInt(r.getString(R.string.toggle_row_en));
+        mToggleRowUri = Integer.parseInt(r.getString(R.string.toggle_row_uri));
+        mToggleRowEmailAddress = Integer.parseInt(r
+                .getString(R.string.toggle_row_emailaddress));
+    }
+
+    public int getInputMode() {
+        return mInputMode;
+    }
+
+    public ToggleStates getToggleStates() {
+        return mToggleStates;
+    }
+
+    public int getSkbLayout() {
+        int layout = (mInputMode & MASK_SKB_LAYOUT);
+
+        switch (layout) {
+        case MASK_SKB_LAYOUT_QWERTY:
+            return R.xml.skb_qwerty;
+        case MASK_SKB_LAYOUT_SYMBOL1:
+            return R.xml.skb_sym1;
+        case MASK_SKB_LAYOUT_SYMBOL2:
+            return R.xml.skb_sym2;
+        case MASK_SKB_LAYOUT_SMILEY:
+            return R.xml.skb_smiley;
+        case MASK_SKB_LAYOUT_PHONE:
+            return R.xml.skb_phone;
+        }
+        return 0;
+    }
+
+    // Return the icon to update.
+    public int switchLanguageWithHkb() {
+        int newInputMode = MODE_HKB_CHINESE;
+        mInputIcon = R.drawable.ime_pinyin;
+
+        if (MODE_HKB_CHINESE == mInputMode) {
+            newInputMode = MODE_HKB_ENGLISH;
+            mInputIcon = R.drawable.ime_en;
+        }
+
+        saveInputMode(newInputMode);
+        return mInputIcon;
+    }
+
+    // Return the icon to update.
+    public int switchModeForUserKey(int userKey) {
+        int newInputMode = MODE_UNSET;
+
+        if (USERDEF_KEYCODE_LANG_2 == userKey) {
+            if (MODE_SKB_CHINESE == mInputMode) {
+                newInputMode = MODE_SKB_ENGLISH_LOWER;
+            } else if (MODE_SKB_ENGLISH_LOWER == mInputMode
+                    || MODE_SKB_ENGLISH_UPPER == mInputMode) {
+                newInputMode = MODE_SKB_CHINESE;
+            } else if (MODE_SKB_SYMBOL1_CN == mInputMode) {
+                newInputMode = MODE_SKB_SYMBOL1_EN;
+            } else if (MODE_SKB_SYMBOL1_EN == mInputMode) {
+                newInputMode = MODE_SKB_SYMBOL1_CN;
+            } else if (MODE_SKB_SYMBOL2_CN == mInputMode) {
+                newInputMode = MODE_SKB_SYMBOL2_EN;
+            } else if (MODE_SKB_SYMBOL2_EN == mInputMode) {
+                newInputMode = MODE_SKB_SYMBOL2_CN;
+            } else if (MODE_SKB_SMILEY == mInputMode) {
+                newInputMode = MODE_SKB_CHINESE;
+            }
+        } else if (USERDEF_KEYCODE_SYM_3 == userKey) {
+            if (MODE_SKB_CHINESE == mInputMode) {
+                newInputMode = MODE_SKB_SYMBOL1_CN;
+            } else if (MODE_SKB_ENGLISH_UPPER == mInputMode
+                    || MODE_SKB_ENGLISH_LOWER == mInputMode) {
+                newInputMode = MODE_SKB_SYMBOL1_EN;
+            } else if (MODE_SKB_SYMBOL1_EN == mInputMode
+                    || MODE_SKB_SYMBOL2_EN == mInputMode) {
+                newInputMode = MODE_SKB_ENGLISH_LOWER;
+            } else if (MODE_SKB_SYMBOL1_CN == mInputMode
+                    || MODE_SKB_SYMBOL2_CN == mInputMode) {
+                newInputMode = MODE_SKB_CHINESE;
+            } else if (MODE_SKB_SMILEY == mInputMode) {
+                newInputMode = MODE_SKB_SYMBOL1_CN;
+            }
+        } else if (USERDEF_KEYCODE_SHIFT_1 == userKey) {
+            if (MODE_SKB_ENGLISH_LOWER == mInputMode) {
+                newInputMode = MODE_SKB_ENGLISH_UPPER;
+            } else if (MODE_SKB_ENGLISH_UPPER == mInputMode) {
+                newInputMode = MODE_SKB_ENGLISH_LOWER;
+            }
+        } else if (USERDEF_KEYCODE_MORE_SYM_5 == userKey) {
+            int sym = (MASK_SKB_LAYOUT & mInputMode);
+            if (MASK_SKB_LAYOUT_SYMBOL1 == sym) {
+                sym = MASK_SKB_LAYOUT_SYMBOL2;
+            } else {
+                sym = MASK_SKB_LAYOUT_SYMBOL1;
+            }
+            newInputMode = ((mInputMode & (~MASK_SKB_LAYOUT)) | sym);
+        } else if (USERDEF_KEYCODE_SMILEY_6 == userKey) {
+            if (MODE_SKB_CHINESE == mInputMode) {
+                newInputMode = MODE_SKB_SMILEY;
+            } else {
+                newInputMode = MODE_SKB_CHINESE;
+            }
+        } else if (USERDEF_KEYCODE_PHONE_SYM_4 == userKey) {
+            if (MODE_SKB_PHONE_NUM == mInputMode) {
+                newInputMode = MODE_SKB_PHONE_SYM;
+            } else {
+                newInputMode = MODE_SKB_PHONE_NUM;
+            }
+        }
+
+        if (newInputMode == mInputMode || MODE_UNSET == newInputMode) {
+            return mInputIcon;
+        }
+
+        saveInputMode(newInputMode);
+        prepareToggleStates(true);
+        return mInputIcon;
+    }
+
+    // Return the icon to update.
+    public int requestInputWithHkb(EditorInfo editorInfo) {
+        mShortMessageField = false;
+        boolean english = false;
+        int newInputMode = MODE_HKB_CHINESE;
+
+        switch (editorInfo.inputType & EditorInfo.TYPE_MASK_CLASS) {
+        case EditorInfo.TYPE_CLASS_NUMBER:
+        case EditorInfo.TYPE_CLASS_PHONE:
+        case EditorInfo.TYPE_CLASS_DATETIME:
+            english = true;
+            break;
+        case EditorInfo.TYPE_CLASS_TEXT:
+            int v = editorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;
+            if (v == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+                    || v == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
+                    || v == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+                    || v == EditorInfo.TYPE_TEXT_VARIATION_URI) {
+                english = true;
+            } else if (v == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
+                mShortMessageField = true;
+            } else if ((editorInfo.imeOptions &
+                    EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEARCH) {
+                newInputMode = MODE_HKB_CHINESE;
+            }
+            break;
+        default:
+        }
+
+        if (english) {
+            // If the application request English mode, we switch to it.
+            newInputMode = MODE_HKB_ENGLISH;
+        } else {
+            // If the application do not request English mode, we will
+            // try to keep the previous mode to input language text.
+            // Because there is not soft keyboard, we need discard all
+            // soft keyboard related information from the previous language
+            // mode.
+            if ((mRecentLauageInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {
+                newInputMode = MODE_HKB_CHINESE;
+            } else {
+                newInputMode = MODE_HKB_ENGLISH;
+            }
+        }
+        mEditorInfo = editorInfo;
+        saveInputMode(newInputMode);
+        prepareToggleStates(false);
+        return mInputIcon;
+    }
+
+    // Return the icon to update.
+    public int requestInputWithSkb(EditorInfo editorInfo) {
+        mShortMessageField = false;
+
+        int newInputMode = MODE_SKB_CHINESE;
+
+        switch (editorInfo.inputType & EditorInfo.TYPE_MASK_CLASS) {
+        case EditorInfo.TYPE_CLASS_NUMBER:
+        case EditorInfo.TYPE_CLASS_DATETIME:
+            newInputMode = MODE_SKB_SYMBOL1_EN;
+            break;
+        case EditorInfo.TYPE_CLASS_PHONE:
+            newInputMode = MODE_SKB_PHONE_NUM;
+            break;
+        case EditorInfo.TYPE_CLASS_TEXT:
+            int v = editorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;
+            if (v == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+                    || v == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
+                    || v == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+                    || v == EditorInfo.TYPE_TEXT_VARIATION_URI) {
+                // If the application request English mode, we switch to it.
+                newInputMode = MODE_SKB_ENGLISH_LOWER;
+            } else if (v == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
+                newInputMode = MODE_SKB_CHINESE;
+                mShortMessageField = true;
+            } else if ((editorInfo.imeOptions &
+                    EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEARCH) {
+                newInputMode = MODE_SKB_CHINESE;
+            } else {
+                // If the application do not request English mode, we will
+                // try to keep the previous mode.
+                int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
+                newInputMode = mInputMode;
+                if (0 == skbLayout) {
+                    if ((mInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {
+                        newInputMode = MODE_SKB_CHINESE;
+                    } else {
+                        newInputMode = MODE_SKB_ENGLISH_LOWER;
+                    }
+                }
+            }
+            break;
+        default:
+            newInputMode = MODE_SKB_CHINESE;
+        }
+
+        mEditorInfo = editorInfo;
+        saveInputMode(newInputMode);
+        prepareToggleStates(true);
+        return mInputIcon;
+    }
+
+    // Return the icon to update.
+    public int requestBackToPreviousSkb() {
+        int layout = (mInputMode & MASK_SKB_LAYOUT);
+        int lastLayout = (mPreviousInputMode & MASK_SKB_LAYOUT);
+        if (0 != layout && 0 != lastLayout) {
+            mInputMode = mPreviousInputMode;
+            saveInputMode(mInputMode);
+            prepareToggleStates(true);
+            return mInputIcon;
+        }
+        return 0;
+    }
+
+    public int getTooggleStateForCnCand() {
+        return mToggleStateCnCand;
+    }
+
+    public boolean isEnglishWithHkb() {
+        return MODE_HKB_ENGLISH == mInputMode;
+    }
+
+    public boolean isEnglishWithSkb() {
+        return MODE_SKB_ENGLISH_LOWER == mInputMode
+                || MODE_SKB_ENGLISH_UPPER == mInputMode;
+    }
+
+    public boolean isEnglishUpperCaseWithSkb() {
+        return MODE_SKB_ENGLISH_UPPER == mInputMode;
+    }
+
+    public boolean isChineseText() {
+        int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
+        if (MASK_SKB_LAYOUT_QWERTY == skbLayout || 0 == skbLayout) {
+            int language = (mInputMode & MASK_LANGUAGE);
+            if (MASK_LANGUAGE_CN == language) return true;
+        }
+        return false;
+    }
+
+    public boolean isChineseTextWithHkb() {
+        int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
+        if (0 == skbLayout) {
+            int language = (mInputMode & MASK_LANGUAGE);
+            if (MASK_LANGUAGE_CN == language) return true;
+        }
+        return false;
+    }
+
+    public boolean isChineseTextWithSkb() {
+        int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
+        if (MASK_SKB_LAYOUT_QWERTY == skbLayout) {
+            int language = (mInputMode & MASK_LANGUAGE);
+            if (MASK_LANGUAGE_CN == language) return true;
+        }
+        return false;
+    }
+
+    public boolean isSymbolWithSkb() {
+        int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
+        if (MASK_SKB_LAYOUT_SYMBOL1 == skbLayout
+                || MASK_SKB_LAYOUT_SYMBOL2 == skbLayout) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isEnterNoramlState() {
+        return mEnterKeyNormal;
+    }
+
+    public boolean tryHandleLongPressSwitch(int keyCode) {
+        if (USERDEF_KEYCODE_LANG_2 == keyCode
+                || USERDEF_KEYCODE_PHONE_SYM_4 == keyCode) {
+            mImeService.showOptionsMenu();
+            return true;
+        }
+        return false;
+    }
+
+    private void saveInputMode(int newInputMode) {
+        mPreviousInputMode = mInputMode;
+        mInputMode = newInputMode;
+
+        int skbLayout = (mInputMode & MASK_SKB_LAYOUT);
+        if (MASK_SKB_LAYOUT_QWERTY == skbLayout) {
+            mRecentLauageInputMode = mInputMode;
+        }
+
+        mInputIcon = R.drawable.ime_pinyin;
+        if (isEnglishWithHkb()) {
+            mInputIcon = R.drawable.ime_en;
+        } else if (isChineseTextWithHkb()) {
+            mInputIcon = R.drawable.ime_pinyin;
+        }
+
+        if (!Environment.getInstance().hasHardKeyboard()) {
+            mInputIcon = 0;
+        }
+    }
+
+    private void prepareToggleStates(boolean needSkb) {
+        mEnterKeyNormal = true;
+        if (!needSkb) return;
+
+        mToggleStates.mQwerty = false;
+        mToggleStates.mKeyStatesNum = 0;
+
+        int states[] = mToggleStates.mKeyStates;
+        int statesNum = 0;
+        // Toggle state for language.
+        int language = (mInputMode & MASK_LANGUAGE);
+        int layout = (mInputMode & MASK_SKB_LAYOUT);
+        int charcase = (mInputMode & MASK_CASE);
+        int variation = mEditorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;
+
+        if (MASK_SKB_LAYOUT_PHONE != layout) {
+            if (MASK_LANGUAGE_CN == language) {
+                // Chinese and Chinese symbol are always the default states,
+                // do not add a toggling operation.
+                if (MASK_SKB_LAYOUT_QWERTY == layout) {
+                    mToggleStates.mQwerty = true;
+                    mToggleStates.mQwertyUpperCase = true;
+                    if (mShortMessageField) {
+                        states[statesNum] = mToggleStateSmiley;
+                        statesNum++;
+                    }
+                }
+            } else if (MASK_LANGUAGE_EN == language) {
+                if (MASK_SKB_LAYOUT_QWERTY == layout) {
+                    mToggleStates.mQwerty = true;
+                    mToggleStates.mQwertyUpperCase = false;
+                    states[statesNum] = mToggleStateEnLower;
+                    if (MASK_CASE_UPPER == charcase) {
+                        mToggleStates.mQwertyUpperCase = true;
+                        states[statesNum] = mToggleStateEnUpper;
+                    }
+                    statesNum++;
+                } else if (MASK_SKB_LAYOUT_SYMBOL1 == layout) {
+                    states[statesNum] = mToggleStateEnSym1;
+                    statesNum++;
+                } else if (MASK_SKB_LAYOUT_SYMBOL2 == layout) {
+                    states[statesNum] = mToggleStateEnSym2;
+                    statesNum++;
+                }
+            }
+
+            // Toggle rows for QWERTY.
+            mToggleStates.mRowIdToEnable = KeyRow.DEFAULT_ROW_ID;
+            if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
+                mToggleStates.mRowIdToEnable = mToggleRowEmailAddress;
+            } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
+                mToggleStates.mRowIdToEnable = mToggleRowUri;
+            } else if (MASK_LANGUAGE_CN == language) {
+                mToggleStates.mRowIdToEnable = mToggleRowCn;
+            } else if (MASK_LANGUAGE_EN == language) {
+                mToggleStates.mRowIdToEnable = mToggleRowEn;
+            }
+        } else {
+            if (MASK_CASE_UPPER == charcase) {
+                states[statesNum] = mToggleStatePhoneSym;
+                statesNum++;
+            }
+        }
+
+        // Toggle state for enter key.
+        int action = mEditorInfo.imeOptions
+                & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
+
+        if (action == EditorInfo.IME_ACTION_GO) {
+            states[statesNum] = mToggleStateGo;
+            statesNum++;
+            mEnterKeyNormal = false;
+        } else if (action == EditorInfo.IME_ACTION_SEARCH) {
+            states[statesNum] = mToggleStateSearch;
+            statesNum++;
+            mEnterKeyNormal = false;
+        } else if (action == EditorInfo.IME_ACTION_SEND) {
+            states[statesNum] = mToggleStateSend;
+            statesNum++;
+            mEnterKeyNormal = false;
+        } else if (action == EditorInfo.IME_ACTION_NEXT) {
+            int f = mEditorInfo.inputType & EditorInfo.TYPE_MASK_FLAGS;
+            if (f != EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
+                states[statesNum] = mToggleStateNext;
+                statesNum++;
+                mEnterKeyNormal = false;
+            }
+        } else if (action == EditorInfo.IME_ACTION_DONE) {
+            states[statesNum] = mToggleStateDone;
+            statesNum++;
+            mEnterKeyNormal = false;
+        }
+        mToggleStates.mKeyStatesNum = statesNum;
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/KeyMapDream.java b/PinyinIME/src/com/android/inputmethod/pinyin/KeyMapDream.java
new file mode 100644
index 0000000..5a95c6f
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/KeyMapDream.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.view.KeyEvent;
+
+/**
+ * Class used to map the symbols on Dream's hardware keyboard to corresponding
+ * Chinese full-width symbols.
+ */
+public class KeyMapDream {
+    // Number of shift bits to store full-width symbols
+    private static final int SHIFT_FWCH = 8;
+    private static final int[] mKeyMap = {
+            KeyEvent.KEYCODE_UNKNOWN,
+            KeyEvent.KEYCODE_SOFT_LEFT,
+            KeyEvent.KEYCODE_SOFT_RIGHT,
+            KeyEvent.KEYCODE_HOME,
+            KeyEvent.KEYCODE_BACK,
+            KeyEvent.KEYCODE_CALL,
+            KeyEvent.KEYCODE_ENDCALL,
+            KeyEvent.KEYCODE_0 | ('\uff09' << SHIFT_FWCH), // )
+            KeyEvent.KEYCODE_1 | ('\uff01' << SHIFT_FWCH), // !
+            KeyEvent.KEYCODE_2 | ('\uff20' << SHIFT_FWCH), // @
+            KeyEvent.KEYCODE_3 | ('\uff03' << SHIFT_FWCH), // #
+            KeyEvent.KEYCODE_4 | ('\uffe5' << SHIFT_FWCH), // $ - fullwidth Yuan
+            KeyEvent.KEYCODE_5 | ('\uff05' << SHIFT_FWCH), // %
+            KeyEvent.KEYCODE_6 | ('\u2026' << SHIFT_FWCH), // ^ - Apostrophe
+            KeyEvent.KEYCODE_7 | ('\uff06' << SHIFT_FWCH), // &
+            KeyEvent.KEYCODE_8 | ('\uff0a' << SHIFT_FWCH), // *
+            KeyEvent.KEYCODE_9 | ('\uff08' << SHIFT_FWCH), // (
+            KeyEvent.KEYCODE_STAR,
+            KeyEvent.KEYCODE_POUND,
+            KeyEvent.KEYCODE_DPAD_UP,
+            KeyEvent.KEYCODE_DPAD_DOWN,
+            KeyEvent.KEYCODE_DPAD_LEFT,
+            KeyEvent.KEYCODE_DPAD_RIGHT,
+            KeyEvent.KEYCODE_DPAD_CENTER,
+            KeyEvent.KEYCODE_VOLUME_UP,
+            KeyEvent.KEYCODE_VOLUME_DOWN,
+            KeyEvent.KEYCODE_POWER,
+            KeyEvent.KEYCODE_CAMERA,
+            KeyEvent.KEYCODE_CLEAR,
+            KeyEvent.KEYCODE_A,
+            KeyEvent.KEYCODE_B | ('\uff3d' << SHIFT_FWCH), // ]
+            KeyEvent.KEYCODE_C | ('\u00a9' << SHIFT_FWCH), // copyright
+            KeyEvent.KEYCODE_D | ('\u3001' << SHIFT_FWCH), // \\
+            KeyEvent.KEYCODE_E | ('_' << SHIFT_FWCH), // _
+            KeyEvent.KEYCODE_F | ('\uff5b' << SHIFT_FWCH), // {
+            KeyEvent.KEYCODE_G | ('\uff5d' << SHIFT_FWCH), // }
+            KeyEvent.KEYCODE_H | ('\uff1a' << SHIFT_FWCH), // :
+            KeyEvent.KEYCODE_I | ('\uff0d' << SHIFT_FWCH), // -
+            KeyEvent.KEYCODE_J | ('\uff1b' << SHIFT_FWCH), // ;
+            KeyEvent.KEYCODE_K | ('\u201c' << SHIFT_FWCH), // "
+            KeyEvent.KEYCODE_L | ('\u2019' << SHIFT_FWCH), // '
+            KeyEvent.KEYCODE_M | ('\u300b' << SHIFT_FWCH), // > - French quotes
+            KeyEvent.KEYCODE_N | ('\u300a' << SHIFT_FWCH), // < - French quotes
+            KeyEvent.KEYCODE_O | ('\uff0b' << SHIFT_FWCH), // +
+            KeyEvent.KEYCODE_P | ('\uff1d' << SHIFT_FWCH), // =
+            KeyEvent.KEYCODE_Q | ('\t' << SHIFT_FWCH), // \t
+            KeyEvent.KEYCODE_R | ('\u00ae' << SHIFT_FWCH), // trademark
+            KeyEvent.KEYCODE_S | ('\uff5c' << SHIFT_FWCH), // |
+            KeyEvent.KEYCODE_T | ('\u20ac' << SHIFT_FWCH), //
+            KeyEvent.KEYCODE_U | ('\u00d7' << SHIFT_FWCH), // multiplier
+            KeyEvent.KEYCODE_V | ('\uff3b' << SHIFT_FWCH), // [
+            KeyEvent.KEYCODE_W | ('\uff40' << SHIFT_FWCH), // `
+            KeyEvent.KEYCODE_X, KeyEvent.KEYCODE_Y | ('\u00f7' << SHIFT_FWCH),
+            KeyEvent.KEYCODE_Z,
+            KeyEvent.KEYCODE_COMMA | ('\uff1f' << SHIFT_FWCH),
+            KeyEvent.KEYCODE_PERIOD | ('\uff0f' << SHIFT_FWCH),
+            KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,
+            KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT,
+            KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_SYM,
+            KeyEvent.KEYCODE_EXPLORER, KeyEvent.KEYCODE_ENVELOPE,
+            KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_DEL,
+            KeyEvent.KEYCODE_GRAVE, KeyEvent.KEYCODE_MINUS,
+            KeyEvent.KEYCODE_EQUALS, KeyEvent.KEYCODE_LEFT_BRACKET,
+            KeyEvent.KEYCODE_RIGHT_BRACKET, KeyEvent.KEYCODE_BACKSLASH,
+            KeyEvent.KEYCODE_SEMICOLON, KeyEvent.KEYCODE_APOSTROPHE,
+            KeyEvent.KEYCODE_SLASH,
+            KeyEvent.KEYCODE_AT | ('\uff5e' << SHIFT_FWCH),
+            KeyEvent.KEYCODE_NUM, KeyEvent.KEYCODE_HEADSETHOOK,
+            KeyEvent.KEYCODE_FOCUS, KeyEvent.KEYCODE_PLUS,
+            KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_NOTIFICATION,
+            KeyEvent.KEYCODE_SEARCH,};
+
+    static public char getChineseLabel(int keyCode) {
+        if (keyCode <= 0 || keyCode >= KeyEvent.getMaxKeyCode()) return 0;
+        assert ((mKeyMap[keyCode] & 0x000000ff) == keyCode);
+        return (char) (mKeyMap[keyCode] >> SHIFT_FWCH);
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/PinyinDecoderService.java b/PinyinIME/src/com/android/inputmethod/pinyin/PinyinDecoderService.java
new file mode 100644
index 0000000..a4a3ac4
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/PinyinDecoderService.java
@@ -0,0 +1,326 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import com.android.inputmethod.pinyin.IPinyinDecoderService;
+
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.Vector;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This class is used to separate the input method kernel in an individual
+ * service so that both IME and IME-syncer can use it.
+ */
+public class PinyinDecoderService extends Service {
+    native static boolean nativeImOpenDecoder(byte fn_sys_dict[],
+            byte fn_usr_dict[]);
+
+    native static boolean nativeImOpenDecoderFd(FileDescriptor fd,
+            long startOffset, long length, byte fn_usr_dict[]);
+
+    native static void nativeImSetMaxLens(int maxSpsLen, int maxHzsLen);
+
+    native static boolean nativeImCloseDecoder();
+
+    native static int nativeImSearch(byte pyBuf[], int pyLen);
+
+    native static int nativeImDelSearch(int pos, boolean is_pos_in_splid,
+            boolean clear_fixed_this_step);
+
+    native static void nativeImResetSearch();
+
+    native static int nativeImAddLetter(byte ch);
+
+    native static String nativeImGetPyStr(boolean decoded);
+
+    native static int nativeImGetPyStrLen(boolean decoded);
+
+    native static int[] nativeImGetSplStart();
+
+    native static String nativeImGetChoice(int choiceId);
+
+    native static int nativeImChoose(int choiceId);
+
+    native static int nativeImCancelLastChoice();
+
+    native static int nativeImGetFixedLen();
+
+    native static boolean nativeImCancelInput();
+
+    native static boolean nativeImFlushCache();
+
+    native static int nativeImGetPredictsNum(String fixedStr);
+
+    native static String nativeImGetPredictItem(int predictNo);
+
+    // Sync related
+    native static String nativeSyncUserDict(byte[] user_dict, String tomerge);
+
+    native static boolean nativeSyncBegin(byte[] user_dict);
+
+    native static boolean nativeSyncFinish();
+
+    native static String nativeSyncGetLemmas();
+
+    native static int nativeSyncPutLemmas(String tomerge);
+
+    native static int nativeSyncGetLastCount();
+
+    native static int nativeSyncGetTotalCount();
+
+    native static boolean nativeSyncClearLastGot();
+
+    native static int nativeSyncGetCapacity();
+
+    private final static int MAX_PATH_FILE_LENGTH = 100;
+    private static boolean inited = false;
+
+    private String mUsr_dict_file;
+
+    static {
+        try {
+            System.loadLibrary("jni_pinyinime");
+        } catch (UnsatisfiedLinkError ule) {
+            Log.e("PinyinDecoderService",
+                    "WARNING: Could not load jni_pinyinime natives");
+        }
+    }
+
+    // Get file name of the specified dictionary
+    private boolean getUsrDictFileName(byte usr_dict[]) {
+        if (null == usr_dict) {
+            return false;
+        }
+
+        for (int i = 0; i < mUsr_dict_file.length(); i++)
+            usr_dict[i] = (byte) mUsr_dict_file.charAt(i);
+        usr_dict[mUsr_dict_file.length()] = 0;
+
+        return true;
+    }
+
+    private void initPinyinEngine() {
+        byte usr_dict[];
+        usr_dict = new byte[MAX_PATH_FILE_LENGTH];
+
+        // Here is how we open a built-in dictionary for access through
+        // a file descriptor...
+        AssetFileDescriptor afd = getResources().openRawResourceFd(
+                R.raw.dict_pinyin);
+        if (Environment.getInstance().needDebug()) {
+            Log
+                    .i("foo", "Dict: start=" + afd.getStartOffset()
+                            + ", length=" + afd.getLength() + ", fd="
+                            + afd.getParcelFileDescriptor());
+        }
+        if (getUsrDictFileName(usr_dict)) {
+            inited = nativeImOpenDecoderFd(afd.getFileDescriptor(), afd
+                    .getStartOffset(), afd.getLength(), usr_dict);
+        }
+        try {
+            afd.close();
+        } catch (IOException e) {
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mUsr_dict_file = getFileStreamPath("usr_dict.dat").getPath();
+        // This is a hack to make sure our "files" directory has been
+        // created.
+        try {
+            openFileOutput("dummy", 0).close();
+        } catch (FileNotFoundException e) {
+        } catch (IOException e) {
+        }
+
+        initPinyinEngine();
+    }
+
+    @Override
+    public void onDestroy() {
+        nativeImCloseDecoder();
+        inited = false;
+        super.onDestroy();
+    }
+
+    private final IPinyinDecoderService.Stub mBinder = new IPinyinDecoderService.Stub() {
+        public int getInt() {
+            return 12345;
+        }
+
+        public void setMaxLens(int maxSpsLen, int maxHzsLen) {
+            nativeImSetMaxLens(maxSpsLen, maxHzsLen);
+        }
+
+        public int imSearch(byte[] pyBuf, int pyLen) {
+            return nativeImSearch(pyBuf, pyLen);
+        }
+
+        public int imDelSearch(int pos, boolean is_pos_in_splid,
+                boolean clear_fixed_this_step) {
+            return nativeImDelSearch(pos, is_pos_in_splid,
+                    clear_fixed_this_step);
+        }
+
+        public void imResetSearch() {
+            nativeImResetSearch();
+        }
+
+        public int imAddLetter(byte ch) {
+            return nativeImAddLetter(ch);
+        }
+
+        public String imGetPyStr(boolean decoded) {
+            return nativeImGetPyStr(decoded);
+        }
+
+        public int imGetPyStrLen(boolean decoded) {
+            return nativeImGetPyStrLen(decoded);
+        }
+
+        public int[] imGetSplStart() {
+            return nativeImGetSplStart();
+        }
+
+        public String imGetChoice(int choiceId) {
+            return nativeImGetChoice(choiceId);
+        }
+
+        public String imGetChoices(int choicesNum) {
+            String retStr = null;
+            for (int i = 0; i < choicesNum; i++) {
+                if (null == retStr)
+                    retStr = nativeImGetChoice(i);
+                else
+                    retStr += " " + nativeImGetChoice(i);
+            }
+            return retStr;
+        }
+
+        public List<String> imGetChoiceList(int choicesStart, int choicesNum,
+                int sentFixedLen) {
+            Vector<String> choiceList = new Vector<String>();
+            for (int i = choicesStart; i < choicesStart + choicesNum; i++) {
+                String retStr = nativeImGetChoice(i);
+                if (0 == i) retStr = retStr.substring(sentFixedLen);
+                choiceList.add(retStr);
+            }
+            return choiceList;
+        }
+
+        public int imChoose(int choiceId) {
+            return nativeImChoose(choiceId);
+        }
+
+        public int imCancelLastChoice() {
+            return nativeImCancelLastChoice();
+        }
+
+        public int imGetFixedLen() {
+            return nativeImGetFixedLen();
+        }
+
+        public boolean imCancelInput() {
+            return nativeImCancelInput();
+        }
+
+        public void imFlushCache() {
+            nativeImFlushCache();
+        }
+
+        public int imGetPredictsNum(String fixedStr) {
+            return nativeImGetPredictsNum(fixedStr);
+        }
+
+        public String imGetPredictItem(int predictNo) {
+            return nativeImGetPredictItem(predictNo);
+        }
+
+        public List<String> imGetPredictList(int predictsStart, int predictsNum) {
+            Vector<String> predictList = new Vector<String>();
+            for (int i = predictsStart; i < predictsStart + predictsNum; i++) {
+                predictList.add(nativeImGetPredictItem(i));
+            }
+            return predictList;
+        }
+
+        public String syncUserDict(String tomerge) {
+            byte usr_dict[];
+            usr_dict = new byte[MAX_PATH_FILE_LENGTH];
+
+            if (getUsrDictFileName(usr_dict)) {
+                return nativeSyncUserDict(usr_dict, tomerge);
+            }
+            return null;
+        }
+
+        public boolean syncBegin() {
+            byte usr_dict[];
+            usr_dict = new byte[MAX_PATH_FILE_LENGTH];
+
+            if (getUsrDictFileName(usr_dict)) {
+                return nativeSyncBegin(usr_dict);
+            }
+            return false;
+        }
+
+        public void syncFinish() {
+            nativeSyncFinish();
+        }
+
+        public int syncPutLemmas(String tomerge) {
+            return nativeSyncPutLemmas(tomerge);
+        }
+
+        public String syncGetLemmas() {
+            return nativeSyncGetLemmas();
+        }
+
+        public int syncGetLastCount() {
+            return nativeSyncGetLastCount();
+        }
+
+        public int syncGetTotalCount() {
+            return nativeSyncGetTotalCount();
+        }
+
+        public void syncClearLastGot() {
+            nativeSyncClearLastGot();
+        }
+
+        public int imSyncGetCapacity() {
+            return nativeSyncGetCapacity();
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/PinyinIME.java b/PinyinIME/src/com/android/inputmethod/pinyin/PinyinIME.java
new file mode 100644
index 0000000..8d7dcce
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/PinyinIME.java
@@ -0,0 +1,2081 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.GestureDetector;
+import android.view.LayoutInflater;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.LayoutParams;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * Main class of the Pinyin input method.
+ */
+public class PinyinIME extends InputMethodService {
+    /**
+     * TAG for debug.
+     */
+    static final String TAG = "PinyinIME";
+
+    /**
+     * If is is true, IME will simulate key events for delete key, and send the
+     * events back to the application.
+     */
+    private static final boolean SIMULATE_KEY_DELETE = true;
+
+    /**
+     * Necessary environment configurations like screen size for this IME.
+     */
+    private Environment mEnvironment;
+
+    /**
+     * Used to switch input mode.
+     */
+    private InputModeSwitcher mInputModeSwitcher;
+
+    /**
+     * Soft keyboard container view to host real soft keyboard view.
+     */
+    private SkbContainer mSkbContainer;
+
+    /**
+     * The floating container which contains the composing view. If necessary,
+     * some other view like candiates container can also be put here.
+     */
+    private LinearLayout mFloatingContainer;
+
+    /**
+     * View to show the composing string.
+     */
+    private ComposingView mComposingView;
+
+    /**
+     * Window to show the composing string.
+     */
+    private PopupWindow mFloatingWindow;
+
+    /**
+     * Used to show the floating window.
+     */
+    private PopupTimer mFloatingWindowTimer = new PopupTimer();
+
+    /**
+     * View to show candidates list.
+     */
+    private CandidatesContainer mCandidatesContainer;
+
+    /**
+     * Balloon used when user presses a candidate.
+     */
+    private BalloonHint mCandidatesBalloon;
+
+    /**
+     * Used to notify the input method when the user touch a candidate.
+     */
+    private ChoiceNotifier mChoiceNotifier;
+
+    /**
+     * Used to notify gestures from soft keyboard.
+     */
+    private OnGestureListener mGestureListenerSkb;
+
+    /**
+     * Used to notify gestures from candidates view.
+     */
+    private OnGestureListener mGestureListenerCandidates;
+
+    /**
+     * The on-screen movement gesture detector for soft keyboard.
+     */
+    private GestureDetector mGestureDetectorSkb;
+
+    /**
+     * The on-screen movement gesture detector for candidates view.
+     */
+    private GestureDetector mGestureDetectorCandidates;
+
+    /**
+     * Option dialog to choose settings and other IMEs.
+     */
+    private AlertDialog mOptionsDialog;
+
+    /**
+     * Connection used to bind the decoding service.
+     */
+    private PinyinDecoderServiceConnection mPinyinDecoderServiceConnection;
+
+    /**
+     * The current IME status.
+     *
+     * @see com.android.inputmethod.pinyin.PinyinIME.ImeState
+     */
+    private ImeState mImeState = ImeState.STATE_IDLE;
+
+    /**
+     * The decoding information, include spelling(Pinyin) string, decoding
+     * result, etc.
+     */
+    private DecodingInfo mDecInfo = new DecodingInfo();
+
+    /**
+     * For English input.
+     */
+    private EnglishInputProcessor mImEn;
+
+    // receive ringer mode changes
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            SoundManager.getInstance(context).updateRingerMode();
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        mEnvironment = Environment.getInstance();
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onCreate.");
+        }
+        super.onCreate();
+
+        startPinyinDecoderService();
+        mImEn = new EnglishInputProcessor();
+        Settings.getInstance(PreferenceManager
+                .getDefaultSharedPreferences(getApplicationContext()));
+
+        mInputModeSwitcher = new InputModeSwitcher(this);
+        mChoiceNotifier = new ChoiceNotifier(this);
+        mGestureListenerSkb = new OnGestureListener(false);
+        mGestureListenerCandidates = new OnGestureListener(true);
+        mGestureDetectorSkb = new GestureDetector(this, mGestureListenerSkb);
+        mGestureDetectorCandidates = new GestureDetector(this,
+                mGestureListenerCandidates);
+
+        mEnvironment.onConfigurationChanged(getResources().getConfiguration(),
+                this);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onDestroy.");
+        }
+        unbindService(mPinyinDecoderServiceConnection);
+        Settings.releaseInstance();
+        super.onDestroy();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        Environment env = Environment.getInstance();
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onConfigurationChanged");
+            Log.d(TAG, "--last config: " + env.getConfiguration().toString());
+            Log.d(TAG, "---new config: " + newConfig.toString());
+        }
+        // We need to change the local environment first so that UI components
+        // can get the environment instance to handle size issues. When
+        // super.onConfigurationChanged() is called, onCreateCandidatesView()
+        // and onCreateInputView() will be executed if necessary.
+        env.onConfigurationChanged(newConfig, this);
+
+        // Clear related UI of the previous configuration.
+        if (null != mSkbContainer) {
+            mSkbContainer.dismissPopups();
+        }
+        if (null != mCandidatesBalloon) {
+            mCandidatesBalloon.dismiss();
+        }
+        super.onConfigurationChanged(newConfig);
+        resetToIdleState(false);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        int count = event.getRepeatCount();
+        if (0 == count) {
+            if (processKey(event, false)) return true;
+        } else {
+            boolean processed = false;
+            while (count > 0) {
+                processed |= processKey(event, true);
+                count--;
+            }
+            if (processed) return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (processKey(event, true)) return true;
+        return super.onKeyUp(keyCode, event);
+    }
+
+    private boolean processKey(KeyEvent event, boolean realAction) {
+        if (ImeState.STATE_BYPASS == mImeState) return false;
+
+        int keyCode = event.getKeyCode();
+        // SHIFT-SPACE is used to switch between Chinese and English
+        // when HKB is on.
+        if (KeyEvent.KEYCODE_SPACE == keyCode && event.isShiftPressed()) {
+            if (!realAction) return true;
+
+            updateIcon(mInputModeSwitcher.switchLanguageWithHkb());
+            resetToIdleState(true);
+
+            int allMetaState = KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON
+                    | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_SHIFT_ON
+                    | KeyEvent.META_SHIFT_LEFT_ON
+                    | KeyEvent.META_SHIFT_RIGHT_ON | KeyEvent.META_SYM_ON;
+            getCurrentInputConnection().clearMetaKeyStates(allMetaState);
+            return true;
+        }
+
+        // If HKB is on to input English, by-pass the key event so that
+        // default key listener will handle it.
+        if (mInputModeSwitcher.isEnglishWithHkb()) {
+            return false;
+        }
+
+        if (processFunctionKeys(keyCode, realAction)) {
+            return true;
+        }
+
+        int keyChar = 0;
+        if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
+            keyChar = keyCode - KeyEvent.KEYCODE_A + 'a';
+        } else if (keyCode >= KeyEvent.KEYCODE_0
+                && keyCode <= KeyEvent.KEYCODE_9) {
+            keyChar = keyCode - KeyEvent.KEYCODE_0 + '0';
+        } else if (keyCode == KeyEvent.KEYCODE_COMMA) {
+            keyChar = ',';
+        } else if (keyCode == KeyEvent.KEYCODE_PERIOD) {
+            keyChar = '.';
+        } else if (keyCode == KeyEvent.KEYCODE_SPACE) {
+            keyChar = ' ';
+        } else if (keyCode == KeyEvent.KEYCODE_APOSTROPHE) {
+            keyChar = '\'';
+        }
+
+        if (mInputModeSwitcher.isEnglishWithSkb()) {
+            return mImEn.processKey(getCurrentInputConnection(), event,
+                    mInputModeSwitcher.isEnglishUpperCaseWithSkb(), realAction);
+        } else if (mInputModeSwitcher.isChineseText()) {
+            if (mImeState == ImeState.STATE_IDLE ||
+                    mImeState == ImeState.STATE_APP_COMPLETION) {
+                mImeState = ImeState.STATE_IDLE;
+                return processStateIdle(keyChar, keyCode, event, realAction);
+            } else if (mImeState == ImeState.STATE_INPUT) {
+                return processStateInput(keyChar, keyCode, event, realAction);
+            } else if (mImeState == ImeState.STATE_PREDICT) {
+                return processStatePredict(keyChar, keyCode, event, realAction);
+            } else if (mImeState == ImeState.STATE_COMPOSING) {
+                return processStateEditComposing(keyChar, keyCode, event,
+                        realAction);
+            }
+        } else {
+            if (0 != keyChar && realAction) {
+                commitResultText(String.valueOf((char) keyChar));
+            }
+        }
+
+        return false;
+    }
+
+    // keyCode can be from both hard key or soft key.
+    private boolean processFunctionKeys(int keyCode, boolean realAction) {
+        // Back key is used to dismiss all popup UI in a soft keyboard.
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            if (isInputViewShown()) {
+                if (mSkbContainer.handleBack(realAction)) return true;
+            }
+        }
+
+        // Chinese related input is handle separately.
+        if (mInputModeSwitcher.isChineseText()) {
+            return false;
+        }
+
+        if (null != mCandidatesContainer && mCandidatesContainer.isShown()
+                && !mDecInfo.isCandidatesListEmpty()) {
+            if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+                if (!realAction) return true;
+
+                chooseCandidate(-1);
+                return true;
+            }
+
+            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
+                if (!realAction) return true;
+                mCandidatesContainer.activeCurseBackward();
+                return true;
+            }
+
+            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+                if (!realAction) return true;
+                mCandidatesContainer.activeCurseForward();
+                return true;
+            }
+
+            if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+                if (!realAction) return true;
+                mCandidatesContainer.pageBackward(false, true);
+                return true;
+            }
+
+            if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+                if (!realAction) return true;
+                mCandidatesContainer.pageForward(false, true);
+                return true;
+            }
+
+            if (keyCode == KeyEvent.KEYCODE_DEL &&
+                    ImeState.STATE_PREDICT == mImeState) {
+                if (!realAction) return true;
+                resetToIdleState(false);
+                return true;
+            }
+        } else {
+            if (keyCode == KeyEvent.KEYCODE_DEL) {
+                if (!realAction) return true;
+                if (SIMULATE_KEY_DELETE) {
+                    simulateKeyEventDownUp(keyCode);
+                } else {
+                    getCurrentInputConnection().deleteSurroundingText(1, 0);
+                }
+                return true;
+            }
+            if (keyCode == KeyEvent.KEYCODE_ENTER) {
+                if (!realAction) return true;
+                sendKeyChar('\n');
+                return true;
+            }
+            if (keyCode == KeyEvent.KEYCODE_SPACE) {
+                if (!realAction) return true;
+                sendKeyChar(' ');
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean processStateIdle(int keyChar, int keyCode, KeyEvent event,
+            boolean realAction) {
+        // In this status, when user presses keys in [a..z], the status will
+        // change to input state.
+        if (keyChar >= 'a' && keyChar <= 'z' && !event.isAltPressed()) {
+            if (!realAction) return true;
+            mDecInfo.addSplChar((char) keyChar, true);
+            chooseAndUpdate(-1);
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+            if (!realAction) return true;
+            if (SIMULATE_KEY_DELETE) {
+                simulateKeyEventDownUp(keyCode);
+            } else {
+                getCurrentInputConnection().deleteSurroundingText(1, 0);
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
+            if (!realAction) return true;
+            sendKeyChar('\n');
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_ALT_LEFT
+                || keyCode == KeyEvent.KEYCODE_ALT_RIGHT
+                || keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+            return true;
+        } else if (event.isAltPressed()) {
+            char fullwidth_char = KeyMapDream.getChineseLabel(keyCode);
+            if (0 != fullwidth_char) {
+                if (realAction) {
+                    String result = String.valueOf(fullwidth_char);
+                    commitResultText(result);
+                }
+                return true;
+            } else {
+                if (keyCode >= KeyEvent.KEYCODE_A
+                        && keyCode <= KeyEvent.KEYCODE_Z) {
+                    return true;
+                }
+            }
+        } else if (keyChar != 0 && keyChar != '\t') {
+            if (realAction) {
+                if (keyChar == ',' || keyChar == '.') {
+                    inputCommaPeriod("", keyChar, false, ImeState.STATE_IDLE);
+                } else {
+                    if (0 != keyChar) {
+                        String result = String.valueOf((char) keyChar);
+                        commitResultText(result);
+                    }
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private boolean processStateInput(int keyChar, int keyCode, KeyEvent event,
+            boolean realAction) {
+        if (keyChar >= 'a' && keyChar <= 'z' || keyChar == '\''
+                && !mDecInfo.charBeforeCursorIsSeparator()
+                || keyCode == KeyEvent.KEYCODE_DEL) {
+            if (!realAction) return true;
+            return processSurfaceChange(keyChar, keyCode);
+        } else if (keyChar == ',' || keyChar == '.') {
+            if (!realAction) return true;
+            inputCommaPeriod(mDecInfo.getCurrentFullSent(mCandidatesContainer
+                    .getActiveCandiatePos()), keyChar, true,
+                    ImeState.STATE_IDLE);
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
+                || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
+                || keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+            if (!realAction) return true;
+
+            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
+                mCandidatesContainer.activeCurseBackward();
+            } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+                mCandidatesContainer.activeCurseForward();
+            } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+                // If it has been the first page, a up key will shift
+                // the state to edit composing string.
+                if (!mCandidatesContainer.pageBackward(false, true)) {
+                    mCandidatesContainer.enableActiveHighlight(false);
+                    changeToStateComposing(true);
+                    updateComposingText(true);
+                }
+            } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+                mCandidatesContainer.pageForward(false, true);
+            }
+            return true;
+        } else if (keyCode >= KeyEvent.KEYCODE_1
+                && keyCode <= KeyEvent.KEYCODE_9) {
+            if (!realAction) return true;
+
+            int activePos = keyCode - KeyEvent.KEYCODE_1;
+            int currentPage = mCandidatesContainer.getCurrentPage();
+            if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {
+                activePos = activePos
+                        + mDecInfo.getCurrentPageStart(currentPage);
+                if (activePos >= 0) {
+                    chooseAndUpdate(activePos);
+                }
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
+            if (!realAction) return true;
+            if (mInputModeSwitcher.isEnterNoramlState()) {
+                commitResultText(mDecInfo.getOrigianlSplStr().toString());
+                resetToIdleState(true);
+            } else {
+                commitResultText(mDecInfo
+                        .getCurrentFullSent(mCandidatesContainer
+                                .getActiveCandiatePos()));
+                sendKeyChar('\n');
+                resetToIdleState(true);
+            }
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+                || keyCode == KeyEvent.KEYCODE_SPACE) {
+            if (!realAction) return true;
+            chooseCandidate(-1);
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_BACK) {
+            if (!realAction) return true;
+            resetToIdleState(true);
+            requestHideSelf(0);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean processStatePredict(int keyChar, int keyCode,
+            KeyEvent event, boolean realAction) {
+        if (!realAction) return true;
+
+        // In this status, when user presses keys in [a..z], the status will
+        // change to input state.
+        if (keyChar >= 'a' && keyChar <= 'z') {
+            changeToStateInput(true);
+            mDecInfo.addSplChar((char) keyChar, true);
+            chooseAndUpdate(-1);
+        } else if (keyChar == ',' || keyChar == '.') {
+            inputCommaPeriod("", keyChar, true, ImeState.STATE_IDLE);
+        } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
+                || keyCode == KeyEvent.KEYCODE_DPAD_DOWN
+                || keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
+                mCandidatesContainer.activeCurseBackward();
+            }
+            if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+                mCandidatesContainer.activeCurseForward();
+            }
+            if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+                mCandidatesContainer.pageBackward(false, true);
+            }
+            if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+                mCandidatesContainer.pageForward(false, true);
+            }
+        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+            resetToIdleState(false);
+        } else if (keyCode == KeyEvent.KEYCODE_BACK) {
+            resetToIdleState(false);
+            requestHideSelf(0);
+        } else if (keyCode >= KeyEvent.KEYCODE_1 
+                && keyCode <= KeyEvent.KEYCODE_9) {
+            int activePos = keyCode - KeyEvent.KEYCODE_1;
+            int currentPage = mCandidatesContainer.getCurrentPage();
+            if (activePos < mDecInfo.getCurrentPageSize(currentPage)) {
+                activePos = activePos
+                        + mDecInfo.getCurrentPageStart(currentPage);
+                if (activePos >= 0) {
+                    chooseAndUpdate(activePos);
+                }
+            }
+        } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
+            sendKeyChar('\n');
+            resetToIdleState(false);
+        } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+                || keyCode == KeyEvent.KEYCODE_SPACE) {
+            chooseCandidate(-1);
+        }
+
+        return true;
+    }
+
+    private boolean processStateEditComposing(int keyChar, int keyCode,
+            KeyEvent event, boolean realAction) {
+        if (!realAction) return true;
+
+        if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+            if (!mDecInfo.selectionFinished()) {
+                changeToStateInput(true);
+            }
+        } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+            mComposingView.moveCursor(keyCode);
+        } else if ((keyCode == KeyEvent.KEYCODE_ENTER && mInputModeSwitcher
+                .isEnterNoramlState())
+                || keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+                || keyCode == KeyEvent.KEYCODE_SPACE) {
+            ComposingView.ComposingStatus cmpsvStatus = mComposingView
+                    .getComposingStatus();
+
+            if (ComposingView.ComposingStatus.SHOW_STRING_LOWERCASE == cmpsvStatus) {
+                String str = mDecInfo.getOrigianlSplStr().toString();
+                if (!tryInputRawUnicode(str)) {
+                    commitResultText(str);
+                }
+            } else if (ComposingView.ComposingStatus.EDIT_PINYIN == cmpsvStatus) {
+                String str = mDecInfo.getComposingStr();
+                if (!tryInputRawUnicode(str)) {
+                    commitResultText(str);
+                }
+            } else {
+                commitResultText(mDecInfo.getComposingStr());
+            }
+            resetToIdleState(false);
+        } else if (keyCode == KeyEvent.KEYCODE_ENTER
+                && !mInputModeSwitcher.isEnterNoramlState()) {
+            String retStr;
+            if (!mDecInfo.isCandidatesListEmpty()) {
+                retStr = mDecInfo.getCurrentFullSent(mCandidatesContainer
+                        .getActiveCandiatePos());
+            } else {
+                retStr = mDecInfo.getComposingStr();
+            }
+            commitResultText(retStr);
+            sendKeyChar('\n');
+            resetToIdleState(false);
+        } else if (keyCode == KeyEvent.KEYCODE_BACK) {
+            resetToIdleState(false);
+            requestHideSelf(0);
+            return true;
+        } else {
+            return processSurfaceChange(keyChar, keyCode);
+        }
+        return true;
+    }
+
+    private boolean tryInputRawUnicode(String str) {
+        if (str.length() > 7) {
+            if (str.substring(0, 7).compareTo("unicode") == 0) {
+                try {
+                    String digitStr = str.substring(7);
+                    int startPos = 0;
+                    int radix = 10;
+                    if (digitStr.length() > 2 && digitStr.charAt(0) == '0'
+                            && digitStr.charAt(1) == 'x') {
+                        startPos = 2;
+                        radix = 16;
+                    }
+                    digitStr = digitStr.substring(startPos);
+                    int unicode = Integer.parseInt(digitStr, radix);
+                    if (unicode > 0) {
+                        char low = (char) (unicode & 0x0000ffff);
+                        char high = (char) ((unicode & 0xffff0000) >> 16);
+                        commitResultText(String.valueOf(low));
+                        if (0 != high) {
+                            commitResultText(String.valueOf(high));
+                        }
+                    }
+                    return true;
+                } catch (NumberFormatException e) {
+                    return false;
+                }
+            } else if (str.substring(str.length() - 7, str.length()).compareTo(
+                    "unicode") == 0) {
+                String resultStr = "";
+                for (int pos = 0; pos < str.length() - 7; pos++) {
+                    if (pos > 0) {
+                        resultStr += " ";
+                    }
+
+                    resultStr += "0x" + Integer.toHexString(str.charAt(pos));
+                }
+                commitResultText(String.valueOf(resultStr));
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean processSurfaceChange(int keyChar, int keyCode) {
+        if (mDecInfo.isSplStrFull() && KeyEvent.KEYCODE_DEL != keyCode) {
+            return true;
+        }
+
+        if ((keyChar >= 'a' && keyChar <= 'z')
+                || (keyChar == '\'' && !mDecInfo.charBeforeCursorIsSeparator())
+                || (((keyChar >= '0' && keyChar <= '9') || keyChar == ' ') && ImeState.STATE_COMPOSING == mImeState)) {
+            mDecInfo.addSplChar((char) keyChar, false);
+            chooseAndUpdate(-1);
+        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+            mDecInfo.prepareDeleteBeforeCursor();
+            chooseAndUpdate(-1);
+        }
+        return true;
+    }
+
+    private void changeToStateComposing(boolean updateUi) {
+        mImeState = ImeState.STATE_COMPOSING;
+        if (!updateUi) return;
+
+        if (null != mSkbContainer && mSkbContainer.isShown()) {
+            mSkbContainer.toggleCandidateMode(true);
+        }
+    }
+
+    private void changeToStateInput(boolean updateUi) {
+        mImeState = ImeState.STATE_INPUT;
+        if (!updateUi) return;
+
+        if (null != mSkbContainer && mSkbContainer.isShown()) {
+            mSkbContainer.toggleCandidateMode(true);
+        }
+        showCandidateWindow(true);
+    }
+
+    private void simulateKeyEventDownUp(int keyCode) {
+        InputConnection ic = getCurrentInputConnection();
+        if (null == ic) return;
+
+        ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+        ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
+    }
+
+    private void commitResultText(String resultText) {
+        InputConnection ic = getCurrentInputConnection();
+        if (null != ic) ic.commitText(resultText, resultText.length());
+        if (null != mComposingView) {
+            mComposingView.setVisibility(View.INVISIBLE);
+            mComposingView.invalidate();
+        }
+    }
+
+    private void updateComposingText(boolean visible) {
+        if (!visible) {
+            mComposingView.setVisibility(View.INVISIBLE);
+        } else {
+            mComposingView.setDecodingInfo(mDecInfo, mImeState);
+            mComposingView.setVisibility(View.VISIBLE);
+        }
+        mComposingView.invalidate();
+    }
+
+    private void inputCommaPeriod(String preEdit, int keyChar,
+            boolean dismissCandWindow, ImeState nextState) {
+        if (keyChar == ',')
+            preEdit += '\uff0c';
+        else if (keyChar == '.')
+            preEdit += '\u3002';
+        else
+            return;
+        commitResultText(preEdit);
+        if (dismissCandWindow) resetCandidateWindow();
+        mImeState = nextState;
+    }
+
+    private void resetToIdleState(boolean resetInlineText) {
+        if (ImeState.STATE_IDLE == mImeState) return;
+
+        mImeState = ImeState.STATE_IDLE;
+        mDecInfo.reset();
+
+        if (null != mComposingView) mComposingView.reset();
+        if (resetInlineText) commitResultText("");
+        resetCandidateWindow();
+    }
+
+    private void chooseAndUpdate(int candId) {
+        if (!mInputModeSwitcher.isChineseText()) {
+            String choice = mDecInfo.getCandidate(candId);
+            if (null != choice) {
+                commitResultText(choice);
+            }
+            resetToIdleState(false);
+            return;
+        }
+
+        if (ImeState.STATE_PREDICT != mImeState) {
+            // Get result candidate list, if choice_id < 0, do a new decoding.
+            // If choice_id >=0, select the candidate, and get the new candidate
+            // list.
+            mDecInfo.chooseDecodingCandidate(candId);
+        } else {
+            // Choose a prediction item.
+            mDecInfo.choosePredictChoice(candId);
+        }
+
+        if (mDecInfo.getComposingStr().length() > 0) {
+            String resultStr;
+            resultStr = mDecInfo.getComposingStrActivePart();
+
+            // choiceId >= 0 means user finishes a choice selection.
+            if (candId >= 0 && mDecInfo.canDoPrediction()) {
+                commitResultText(resultStr);
+                mImeState = ImeState.STATE_PREDICT;
+                if (null != mSkbContainer && mSkbContainer.isShown()) {
+                    mSkbContainer.toggleCandidateMode(false);
+                }
+                // Try to get the prediction list.
+                if (Settings.getPrediction()) {
+                    InputConnection ic = getCurrentInputConnection();
+                    if (null != ic) {
+                        CharSequence cs = ic.getTextBeforeCursor(3, 0);
+                        if (null != cs) {
+                            mDecInfo.preparePredicts(cs);
+                        }
+                    }
+                } else {
+                    mDecInfo.resetCandidates();
+                }
+
+                if (mDecInfo.mCandidatesList.size() > 0) {
+                    showCandidateWindow(false);
+                } else {
+                    resetToIdleState(false);
+                }
+            } else {
+                if (ImeState.STATE_IDLE == mImeState) {
+                    if (mDecInfo.getSplStrDecodedLen() == 0) {
+                        changeToStateComposing(true);
+                    } else {
+                        changeToStateInput(true);
+                    }
+                } else {
+                    if (mDecInfo.selectionFinished()) {
+                        changeToStateComposing(true);
+                    }
+                }
+                showCandidateWindow(true);
+            }
+        } else {
+            resetToIdleState(false);
+        }
+    }
+
+    // If activeCandNo is less than 0, get the current active candidate number
+    // from candidate view, otherwise use activeCandNo.
+    private void chooseCandidate(int activeCandNo) {
+        if (activeCandNo < 0) {
+            activeCandNo = mCandidatesContainer.getActiveCandiatePos();
+        }
+        if (activeCandNo >= 0) {
+            chooseAndUpdate(activeCandNo);
+        }
+    }
+
+    private boolean startPinyinDecoderService() {
+        if (null == mDecInfo.mIPinyinDecoderService) {
+            Intent serviceIntent = new Intent();
+            serviceIntent.setClass(this, PinyinDecoderService.class);
+
+            if (null == mPinyinDecoderServiceConnection) {
+                mPinyinDecoderServiceConnection = new PinyinDecoderServiceConnection();
+            }
+
+            // Bind service
+            if (bindService(serviceIntent, mPinyinDecoderServiceConnection,
+                    Context.BIND_AUTO_CREATE)) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public View onCreateCandidatesView() {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onCreateCandidatesView.");
+        }
+
+        LayoutInflater inflater = getLayoutInflater();
+        // Inflate the floating container view
+        mFloatingContainer = (LinearLayout) inflater.inflate(
+                R.layout.floating_container, null);
+
+        // The first child is the composing view.
+        mComposingView = (ComposingView) mFloatingContainer.getChildAt(0);
+
+        mCandidatesContainer = (CandidatesContainer) inflater.inflate(
+                R.layout.candidates_container, null);
+
+        // Create balloon hint for candidates view.
+        mCandidatesBalloon = new BalloonHint(this, mCandidatesContainer,
+                MeasureSpec.UNSPECIFIED);
+        mCandidatesBalloon.setBalloonBackground(getResources().getDrawable(
+                R.drawable.candidate_balloon_bg));
+        mCandidatesContainer.initialize(mChoiceNotifier, mCandidatesBalloon,
+                mGestureDetectorCandidates);
+
+        // The floating window
+        if (null != mFloatingWindow && mFloatingWindow.isShowing()) {
+            mFloatingWindowTimer.cancelShowing();
+            mFloatingWindow.dismiss();
+        }
+        mFloatingWindow = new PopupWindow(this);
+        mFloatingWindow.setClippingEnabled(false);
+        mFloatingWindow.setBackgroundDrawable(null);
+        mFloatingWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+        mFloatingWindow.setContentView(mFloatingContainer);
+
+        setCandidatesViewShown(true);
+        return mCandidatesContainer;
+    }
+
+    public void responseSoftKeyEvent(SoftKey sKey) {
+        if (null == sKey) return;
+
+        InputConnection ic = getCurrentInputConnection();
+        if (ic == null) return;
+
+        int keyCode = sKey.getKeyCode();
+        // Process some general keys, including KEYCODE_DEL, KEYCODE_SPACE,
+        // KEYCODE_ENTER and KEYCODE_DPAD_CENTER.
+        if (sKey.isKeyCodeKey()) {
+            if (processFunctionKeys(keyCode, true)) return;
+        }
+
+        if (sKey.isUserDefKey()) {
+            updateIcon(mInputModeSwitcher.switchModeForUserKey(keyCode));
+            resetToIdleState(true);
+            mSkbContainer.updateInputMode();
+        } else {
+            if (sKey.isKeyCodeKey()) {
+                KeyEvent eDown = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+                        keyCode, 0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
+                KeyEvent eUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode,
+                        0, 0, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
+
+                onKeyDown(keyCode, eDown);
+                onKeyUp(keyCode, eUp);
+            } else if (sKey.isUniStrKey()) {
+                boolean kUsed = false;
+                String keyLabel = sKey.getKeyLabel();
+                if (mInputModeSwitcher.isChineseTextWithSkb()
+                        && (ImeState.STATE_INPUT == mImeState || ImeState.STATE_COMPOSING == mImeState)) {
+                    if (mDecInfo.length() > 0 && keyLabel.length() == 1
+                            && keyLabel.charAt(0) == '\'') {
+                        processSurfaceChange('\'', 0);
+                        kUsed = true;
+                    }
+                }
+                if (!kUsed) {
+                    if (ImeState.STATE_INPUT == mImeState) {
+                        commitResultText(mDecInfo
+                                .getCurrentFullSent(mCandidatesContainer
+                                        .getActiveCandiatePos()));
+                    } else if (ImeState.STATE_COMPOSING == mImeState) {
+                        commitResultText(mDecInfo.getComposingStr());
+                    }
+                    commitResultText(keyLabel);
+                    resetToIdleState(false);
+                }
+            }
+
+            // If the current soft keyboard is not sticky, IME needs to go
+            // back to the previous soft keyboard automatically.
+            if (!mSkbContainer.isCurrentSkbSticky()) {
+                updateIcon(mInputModeSwitcher.requestBackToPreviousSkb());
+                resetToIdleState(true);
+                mSkbContainer.updateInputMode();
+            }
+        }
+    }
+
+    private void showCandidateWindow(boolean showComposingView) {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "Candidates window is shown. Parent = "
+                    + mCandidatesContainer);
+        }
+
+        setCandidatesViewShown(true);
+
+        if (null != mSkbContainer) mSkbContainer.requestLayout();
+
+        if (null == mCandidatesContainer) {
+            resetToIdleState(false);
+            return;
+        }
+
+        updateComposingText(showComposingView);
+        mCandidatesContainer.showCandidates(mDecInfo,
+                ImeState.STATE_COMPOSING != mImeState);
+        mFloatingWindowTimer.postShowFloatingWindow();
+    }
+
+    private void dismissCandidateWindow() {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "Candidates window is to be dismissed");
+        }
+        if (null == mCandidatesContainer) return;
+        try {
+            mFloatingWindowTimer.cancelShowing();
+            mFloatingWindow.dismiss();
+        } catch (Exception e) {
+            Log.e(TAG, "Fail to show the PopupWindow.");
+        }
+        setCandidatesViewShown(false);
+
+        if (null != mSkbContainer && mSkbContainer.isShown()) {
+            mSkbContainer.toggleCandidateMode(false);
+        }
+    }
+
+    private void resetCandidateWindow() {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "Candidates window is to be reset");
+        }
+        if (null == mCandidatesContainer) return;
+        try {
+            mFloatingWindowTimer.cancelShowing();
+            mFloatingWindow.dismiss();
+        } catch (Exception e) {
+            Log.e(TAG, "Fail to show the PopupWindow.");
+        }
+
+        if (null != mSkbContainer && mSkbContainer.isShown()) {
+            mSkbContainer.toggleCandidateMode(false);
+        }
+
+        mDecInfo.resetCandidates();
+
+        if (null != mCandidatesContainer && mCandidatesContainer.isShown()) {
+            showCandidateWindow(false);
+        }
+    }
+
+    private void updateIcon(int iconId) {
+        if (iconId > 0) {
+            showStatusIcon(iconId);
+        } else {
+            hideStatusIcon();
+        }
+    }
+
+    @Override
+    public View onCreateInputView() {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onCreateInputView.");
+        }
+        LayoutInflater inflater = getLayoutInflater();
+        mSkbContainer = (SkbContainer) inflater.inflate(R.layout.skb_container,
+                null);
+        mSkbContainer.setService(this);
+        mSkbContainer.setInputModeSwitcher(mInputModeSwitcher);
+        mSkbContainer.setGestureDetector(mGestureDetectorSkb);
+        return mSkbContainer;
+    }
+
+    @Override
+    public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onStartInput " + " ccontentType: "
+                    + String.valueOf(editorInfo.inputType) + " Restarting:"
+                    + String.valueOf(restarting));
+        }
+        updateIcon(mInputModeSwitcher.requestInputWithHkb(editorInfo));
+        resetToIdleState(false);
+    }
+
+    @Override
+    public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onStartInputView " + " contentType: "
+                    + String.valueOf(editorInfo.inputType) + " Restarting:"
+                    + String.valueOf(restarting));
+        }
+        updateIcon(mInputModeSwitcher.requestInputWithSkb(editorInfo));
+        resetToIdleState(false);
+        mSkbContainer.updateInputMode();
+        setCandidatesViewShown(false);
+    }
+
+    @Override
+    public void onFinishInputView(boolean finishingInput) {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onFinishInputView.");
+        }
+        resetToIdleState(false);
+        super.onFinishInputView(finishingInput);
+    }
+
+    @Override
+    public void onFinishInput() {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onFinishInput.");
+        }
+        resetToIdleState(false);
+        super.onFinishInput();
+    }
+
+    @Override
+    public void onFinishCandidatesView(boolean finishingInput) {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "onFinishCandidateView.");
+        }
+        resetToIdleState(false);
+        super.onFinishCandidatesView(finishingInput);
+    }
+
+    @Override public void onDisplayCompletions(CompletionInfo[] completions) {
+        if (!isFullscreenMode()) return;
+        if (null == completions || completions.length <= 0) return;
+        if (null == mSkbContainer || !mSkbContainer.isShown()) return;
+
+        if (!mInputModeSwitcher.isChineseText() ||
+                ImeState.STATE_IDLE == mImeState ||
+                ImeState.STATE_PREDICT == mImeState) {
+            mImeState = ImeState.STATE_APP_COMPLETION;
+            mDecInfo.prepareAppCompletions(completions);
+            showCandidateWindow(false);
+        }
+    }
+
+    private void onChoiceTouched(int activeCandNo) {
+        if (mImeState == ImeState.STATE_COMPOSING) {
+            changeToStateInput(true);
+        } else if (mImeState == ImeState.STATE_INPUT
+                || mImeState == ImeState.STATE_PREDICT) {
+            chooseCandidate(activeCandNo);
+        } else if (mImeState == ImeState.STATE_APP_COMPLETION) {
+            if (null != mDecInfo.mAppCompletions && activeCandNo >= 0 &&
+                    activeCandNo < mDecInfo.mAppCompletions.length) {
+                CompletionInfo ci = mDecInfo.mAppCompletions[activeCandNo];
+                if (null != ci) {
+                    InputConnection ic = getCurrentInputConnection();
+                    ic.commitCompletion(ci);
+                }
+            }
+            resetToIdleState(false);
+        }
+    }
+
+    @Override
+    public void requestHideSelf(int flags) {
+        if (mEnvironment.needDebug()) {
+            Log.d(TAG, "DimissSoftInput.");
+        }
+        dismissCandidateWindow();
+        if (null != mSkbContainer && mSkbContainer.isShown()) {
+            mSkbContainer.dismissPopups();
+        }
+        super.requestHideSelf(flags);
+    }
+
+    public void showOptionsMenu() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setCancelable(true);
+        builder.setIcon(R.drawable.app_icon);
+        builder.setNegativeButton(android.R.string.cancel, null);
+        CharSequence itemSettings = getString(R.string.ime_settings_activity_name);
+        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 0:
+                            launchSettings();
+                            break;
+                        case 1:
+                            InputMethodManager.getInstance(PinyinIME.this)
+                                    .showInputMethodPicker();
+                            break;
+                        }
+                    }
+                });
+        builder.setTitle(getString(R.string.ime_name));
+        mOptionsDialog = builder.create();
+        Window window = mOptionsDialog.getWindow();
+        WindowManager.LayoutParams lp = window.getAttributes();
+        lp.token = mSkbContainer.getWindowToken();
+        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+        window.setAttributes(lp);
+        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        mOptionsDialog.show();
+    }
+
+    private void launchSettings() {
+        Intent intent = new Intent();
+        intent.setClass(PinyinIME.this, SettingsActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
+    }
+
+    private class PopupTimer extends Handler implements Runnable {
+        private int mParentLocation[] = new int[2];
+
+        void postShowFloatingWindow() {
+            mFloatingContainer.measure(LayoutParams.WRAP_CONTENT,
+                    LayoutParams.WRAP_CONTENT);
+            mFloatingWindow.setWidth(mFloatingContainer.getMeasuredWidth());
+            mFloatingWindow.setHeight(mFloatingContainer.getMeasuredHeight());
+            post(this);
+        }
+
+        void cancelShowing() {
+            if (mFloatingWindow.isShowing()) {
+                mFloatingWindow.dismiss();
+            }
+            removeCallbacks(this);
+        }
+
+        public void run() {
+            mCandidatesContainer.getLocationInWindow(mParentLocation);
+
+            if (!mFloatingWindow.isShowing()) {
+                mFloatingWindow.showAtLocation(mCandidatesContainer,
+                        Gravity.LEFT | Gravity.TOP, mParentLocation[0],
+                        mParentLocation[1] -mFloatingWindow.getHeight());
+            } else {
+                mFloatingWindow
+                .update(mParentLocation[0],
+                        mParentLocation[1] - mFloatingWindow.getHeight(),
+                        mFloatingWindow.getWidth(),
+                        mFloatingWindow.getHeight());
+            }
+        }
+    }
+
+    /**
+     * Used to notify IME that the user selects a candidate or performs an
+     * gesture.
+     */
+    public class ChoiceNotifier extends Handler implements
+            CandidateViewListener {
+        PinyinIME mIme;
+
+        ChoiceNotifier(PinyinIME ime) {
+            mIme = ime;
+        }
+
+        public void onClickChoice(int choiceId) {
+            if (choiceId >= 0) {
+                mIme.onChoiceTouched(choiceId);
+            }
+        }
+
+        public void onToLeftGesture() {
+            if (ImeState.STATE_COMPOSING == mImeState) {
+                changeToStateInput(true);
+            }
+            mCandidatesContainer.pageForward(true, false);
+        }
+
+        public void onToRightGesture() {
+            if (ImeState.STATE_COMPOSING == mImeState) {
+                changeToStateInput(true);
+            }
+            mCandidatesContainer.pageBackward(true, false);
+        }
+
+        public void onToTopGesture() {
+        }
+
+        public void onToBottomGesture() {
+        }
+    }
+
+    public class OnGestureListener extends
+            GestureDetector.SimpleOnGestureListener {
+        /**
+         * When user presses and drags, the minimum x-distance to make a
+         * response to the drag event.
+         */
+        private static final int MIN_X_FOR_DRAG = 60;
+
+        /**
+         * When user presses and drags, the minimum y-distance to make a
+         * response to the drag event.
+         */
+        private static final int MIN_Y_FOR_DRAG = 40;
+
+        /**
+         * Velocity threshold for a screen-move gesture. If the minimum
+         * x-velocity is less than it, no gesture.
+         */
+        static private final float VELOCITY_THRESHOLD_X1 = 0.3f;
+
+        /**
+         * Velocity threshold for a screen-move gesture. If the maximum
+         * x-velocity is less than it, no gesture.
+         */
+        static private final float VELOCITY_THRESHOLD_X2 = 0.7f;
+
+        /**
+         * Velocity threshold for a screen-move gesture. If the minimum
+         * y-velocity is less than it, no gesture.
+         */
+        static private final float VELOCITY_THRESHOLD_Y1 = 0.2f;
+
+        /**
+         * Velocity threshold for a screen-move gesture. If the maximum
+         * y-velocity is less than it, no gesture.
+         */
+        static private final float VELOCITY_THRESHOLD_Y2 = 0.45f;
+
+        /** If it false, we will not response detected gestures. */
+        private boolean mReponseGestures;
+
+        /** The minimum X velocity observed in the gesture. */
+        private float mMinVelocityX = Float.MAX_VALUE;
+
+        /** The minimum Y velocity observed in the gesture. */
+        private float mMinVelocityY = Float.MAX_VALUE;
+
+        /** The first down time for the series of touch events for an action. */
+        private long mTimeDown;
+
+        /** The last time when onScroll() is called. */
+        private long mTimeLastOnScroll;
+
+        /** This flag used to indicate that this gesture is not a gesture. */
+        private boolean mNotGesture;
+
+        /** This flag used to indicate that this gesture has been recognized. */
+        private boolean mGestureRecognized;
+
+        public OnGestureListener(boolean reponseGestures) {
+            mReponseGestures = reponseGestures;
+        }
+
+        @Override
+        public boolean onDown(MotionEvent e) {
+            mMinVelocityX = Integer.MAX_VALUE;
+            mMinVelocityY = Integer.MAX_VALUE;
+            mTimeDown = e.getEventTime();
+            mTimeLastOnScroll = mTimeDown;
+            mNotGesture = false;
+            mGestureRecognized = false;
+            return false;
+        }
+
+        @Override
+        public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                float distanceX, float distanceY) {
+            if (mNotGesture) return false;
+            if (mGestureRecognized) return true;
+
+            if (Math.abs(e1.getX() - e2.getX()) < MIN_X_FOR_DRAG
+                    && Math.abs(e1.getY() - e2.getY()) < MIN_Y_FOR_DRAG)
+                return false;
+
+            long timeNow = e2.getEventTime();
+            long spanTotal = timeNow - mTimeDown;
+            long spanThis = timeNow - mTimeLastOnScroll;
+            if (0 == spanTotal) spanTotal = 1;
+            if (0 == spanThis) spanThis = 1;
+
+            float vXTotal = (e2.getX() - e1.getX()) / spanTotal;
+            float vYTotal = (e2.getY() - e1.getY()) / spanTotal;
+
+            // The distances are from the current point to the previous one.
+            float vXThis = -distanceX / spanThis;
+            float vYThis = -distanceY / spanThis;
+
+            float kX = vXTotal * vXThis;
+            float kY = vYTotal * vYThis;
+            float k1 = kX + kY;
+            float k2 = Math.abs(kX) + Math.abs(kY);
+
+            if (k1 / k2 < 0.8) {
+                mNotGesture = true;
+                return false;
+            }
+            float absVXTotal = Math.abs(vXTotal);
+            float absVYTotal = Math.abs(vYTotal);
+            if (absVXTotal < mMinVelocityX) {
+                mMinVelocityX = absVXTotal;
+            }
+            if (absVYTotal < mMinVelocityY) {
+                mMinVelocityY = absVYTotal;
+            }
+
+            if (mMinVelocityX < VELOCITY_THRESHOLD_X1
+                    && mMinVelocityY < VELOCITY_THRESHOLD_Y1) {
+                mNotGesture = true;
+                return false;
+            }
+
+            if (vXTotal > VELOCITY_THRESHOLD_X2
+                    && absVYTotal < VELOCITY_THRESHOLD_Y2) {
+                if (mReponseGestures) onDirectionGesture(Gravity.RIGHT);
+                mGestureRecognized = true;
+            } else if (vXTotal < -VELOCITY_THRESHOLD_X2
+                    && absVYTotal < VELOCITY_THRESHOLD_Y2) {
+                if (mReponseGestures) onDirectionGesture(Gravity.LEFT);
+                mGestureRecognized = true;
+            } else if (vYTotal > VELOCITY_THRESHOLD_Y2
+                    && absVXTotal < VELOCITY_THRESHOLD_X2) {
+                if (mReponseGestures) onDirectionGesture(Gravity.BOTTOM);
+                mGestureRecognized = true;
+            } else if (vYTotal < -VELOCITY_THRESHOLD_Y2
+                    && absVXTotal < VELOCITY_THRESHOLD_X2) {
+                if (mReponseGestures) onDirectionGesture(Gravity.TOP);
+                mGestureRecognized = true;
+            }
+
+            mTimeLastOnScroll = timeNow;
+            return mGestureRecognized;
+        }
+
+        @Override
+        public boolean onFling(MotionEvent me1, MotionEvent me2,
+                float velocityX, float velocityY) {
+            return mGestureRecognized;
+        }
+
+        public void onDirectionGesture(int gravity) {
+            if (Gravity.NO_GRAVITY == gravity) {
+                return;
+            }
+
+            if (Gravity.LEFT == gravity || Gravity.RIGHT == gravity) {
+                if (mCandidatesContainer.isShown()) {
+                    if (Gravity.LEFT == gravity) {
+                        mCandidatesContainer.pageForward(true, true);
+                    } else {
+                        mCandidatesContainer.pageBackward(true, true);
+                    }
+                    return;
+                }
+            }
+        }
+    }
+
+    /**
+     * Connection used for binding to the Pinyin decoding service.
+     */
+    public class PinyinDecoderServiceConnection implements ServiceConnection {
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mDecInfo.mIPinyinDecoderService = IPinyinDecoderService.Stub
+                    .asInterface(service);
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+        }
+    }
+
+    public enum ImeState {
+        STATE_BYPASS, STATE_IDLE, STATE_INPUT, STATE_COMPOSING, STATE_PREDICT,
+        STATE_APP_COMPLETION
+    }
+
+    public class DecodingInfo {
+        /**
+         * Maximum length of the Pinyin string
+         */
+        private static final int PY_STRING_MAX = 28;
+
+        /**
+         * Maximum number of candidates to display in one page.
+         */
+        private static final int MAX_PAGE_SIZE_DISPLAY = 10;
+
+        /**
+         * Spelling (Pinyin) string.
+         */
+        private StringBuffer mSurface;
+
+        /**
+         * Byte buffer used as the Pinyin string parameter for native function
+         * call.
+         */
+        private byte mPyBuf[];
+
+        /**
+         * The length of surface string successfully decoded by engine.
+         */
+        private int mSurfaceDecodedLen;
+
+        /**
+         * Composing string.
+         */
+        private String mComposingStr;
+
+        /**
+         * Length of the active composing string.
+         */
+        private int mActiveCmpsLen;
+
+        /**
+         * Composing string for display, it is copied from mComposingStr, and
+         * add spaces between spellings.
+         **/
+        private String mComposingStrDisplay;
+
+        /**
+         * Length of the active composing string for display.
+         */
+        private int mActiveCmpsDisplayLen;
+
+        /**
+         * The first full sentence choice.
+         */
+        private String mFullSent;
+
+        /**
+         * Number of characters which have been fixed.
+         */
+        private int mFixedLen;
+
+        /**
+         * If this flag is true, selection is finished.
+         */
+        private boolean mFinishSelection;
+
+        /**
+         * The starting position for each spelling. The first one is the number
+         * of the real starting position elements.
+         */
+        private int mSplStart[];
+
+        /**
+         * Editing cursor in mSurface.
+         */
+        private int mCursorPos;
+
+        /**
+         * Remote Pinyin-to-Hanzi decoding engine service.
+         */
+        private IPinyinDecoderService mIPinyinDecoderService;
+
+        /**
+         * The complication information suggested by application.
+         */
+        private CompletionInfo[] mAppCompletions;
+
+        /**
+         * The total number of choices for display. The list may only contains
+         * the first part. If user tries to navigate to next page which is not
+         * in the result list, we need to get these items.
+         **/
+        public int mTotalChoicesNum;
+
+        /**
+         * Candidate list. The first one is the full-sentence candidate.
+         */
+        public List<String> mCandidatesList = new Vector<String>();
+
+        /**
+         * Element i stores the starting position of page i.
+         */
+        public Vector<Integer> mPageStart = new Vector<Integer>();
+
+        /**
+         * Element i stores the number of characters to page i.
+         */
+        public Vector<Integer> mCnToPage = new Vector<Integer>();
+
+        /**
+         * The position to delete in Pinyin string. If it is less than 0, IME
+         * will do an incremental search, otherwise IME will do a deletion
+         * operation. if {@link #mIsPosInSpl} is true, IME will delete the whole
+         * string for mPosDelSpl-th spelling, otherwise it will only delete
+         * mPosDelSpl-th character in the Pinyin string.
+         */
+        public int mPosDelSpl = -1;
+
+        /**
+         * If {@link #mPosDelSpl} is big than or equal to 0, this member is used
+         * to indicate that whether the postion is counted in spelling id or
+         * character.
+         */
+        public boolean mIsPosInSpl;
+
+        public DecodingInfo() {
+            mSurface = new StringBuffer();
+            mSurfaceDecodedLen = 0;
+        }
+
+        public void reset() {
+            mSurface.delete(0, mSurface.length());
+            mSurfaceDecodedLen = 0;
+            mCursorPos = 0;
+            mFullSent = "";
+            mFixedLen = 0;
+            mFinishSelection = false;
+            mComposingStr = "";
+            mComposingStrDisplay = "";
+            mActiveCmpsLen = 0;
+            mActiveCmpsDisplayLen = 0;
+
+            resetCandidates();
+        }
+
+        public boolean isCandidatesListEmpty() {
+            return mCandidatesList.size() == 0;
+        }
+
+        public boolean isSplStrFull() {
+            if (mSurface.length() >= PY_STRING_MAX - 1) return true;
+            return false;
+        }
+
+        public void addSplChar(char ch, boolean reset) {
+            if (reset) {
+                mSurface.delete(0, mSurface.length());
+                mSurfaceDecodedLen = 0;
+                mCursorPos = 0;
+                try {
+                    mIPinyinDecoderService.imResetSearch();
+                } catch (RemoteException e) {
+                }
+            }
+            mSurface.insert(mCursorPos, ch);
+            mCursorPos++;
+        }
+
+        // Prepare to delete before cursor. We may delete a spelling char if
+        // the cursor is in the range of unfixed part, delete a whole spelling
+        // if the cursor in inside the range of the fixed part.
+        // This function only marks the position used to delete.
+        public void prepareDeleteBeforeCursor() {
+            if (mCursorPos > 0) {
+                int pos;
+                for (pos = 0; pos < mFixedLen; pos++) {
+                    if (mSplStart[pos + 2] >= mCursorPos
+                            && mSplStart[pos + 1] < mCursorPos) {
+                        mPosDelSpl = pos;
+                        mCursorPos = mSplStart[pos + 1];
+                        mIsPosInSpl = true;
+                        break;
+                    }
+                }
+                if (mPosDelSpl < 0) {
+                    mPosDelSpl = mCursorPos - 1;
+                    mCursorPos--;
+                    mIsPosInSpl = false;
+                }
+            }
+        }
+
+        public int length() {
+            return mSurface.length();
+        }
+
+        public char charAt(int index) {
+            return mSurface.charAt(index);
+        }
+
+        public StringBuffer getOrigianlSplStr() {
+            return mSurface;
+        }
+
+        public int getSplStrDecodedLen() {
+            return mSurfaceDecodedLen;
+        }
+
+        public int[] getSplStart() {
+            return mSplStart;
+        }
+
+        public String getComposingStr() {
+            return mComposingStr;
+        }
+
+        public String getComposingStrActivePart() {
+            assert (mActiveCmpsLen <= mComposingStr.length());
+            return mComposingStr.substring(0, mActiveCmpsLen);
+        }
+
+        public int getActiveCmpsLen() {
+            return mActiveCmpsLen;
+        }
+
+        public String getComposingStrForDisplay() {
+            return mComposingStrDisplay;
+        }
+
+        public int getActiveCmpsDisplayLen() {
+            return mActiveCmpsDisplayLen;
+        }
+
+        public String getFullSent() {
+            return mFullSent;
+        }
+
+        public String getCurrentFullSent(int activeCandPos) {
+            String retStr = mFullSent.substring(0, mFixedLen);
+            retStr += mCandidatesList.get(activeCandPos);
+            return retStr;
+        }
+
+        public void resetCandidates() {
+            mCandidatesList.clear();
+            mTotalChoicesNum = 0;
+
+            mPageStart.clear();
+            mPageStart.add(0);
+            mCnToPage.clear();
+            mCnToPage.add(0);
+        }
+
+        public boolean candidatesFromApp() {
+            return ImeState.STATE_APP_COMPLETION == mImeState;
+        }
+
+        public boolean canDoPrediction() {
+            return mComposingStr.length() == mFixedLen;
+        }
+
+        public boolean selectionFinished() {
+            return mFinishSelection;
+        }
+
+        // After the user chooses a candidate, input method will do a
+        // re-decoding and give the new candidate list.
+        // If candidate id is less than 0, means user is inputting Pinyin,
+        // not selecting any choice.
+        private void chooseDecodingCandidate(int candId) {
+            if (mImeState != ImeState.STATE_PREDICT) {
+                resetCandidates();
+                int totalChoicesNum = 0;
+                try {
+                    if (candId < 0) {
+                        if (length() == 0) {
+                            totalChoicesNum = 0;
+                        } else {
+                            if (mPyBuf == null)
+                                mPyBuf = new byte[PY_STRING_MAX];
+                            for (int i = 0; i < length(); i++)
+                                mPyBuf[i] = (byte) charAt(i);
+                            mPyBuf[length()] = 0;
+
+                            if (mPosDelSpl < 0) {
+                                totalChoicesNum = mIPinyinDecoderService
+                                        .imSearch(mPyBuf, length());
+                            } else {
+                                boolean clear_fixed_this_step = true;
+                                if (ImeState.STATE_COMPOSING == mImeState) {
+                                    clear_fixed_this_step = false;
+                                }
+                                totalChoicesNum = mIPinyinDecoderService
+                                        .imDelSearch(mPosDelSpl, mIsPosInSpl,
+                                                clear_fixed_this_step);
+                                mPosDelSpl = -1;
+                            }
+                        }
+                    } else {
+                        totalChoicesNum = mIPinyinDecoderService
+                                .imChoose(candId);
+                    }
+                } catch (RemoteException e) {
+                }
+                updateDecInfoForSearch(totalChoicesNum);
+            }
+        }
+
+        private void updateDecInfoForSearch(int totalChoicesNum) {
+            mTotalChoicesNum = totalChoicesNum;
+            if (mTotalChoicesNum < 0) {
+                mTotalChoicesNum = 0;
+                return;
+            }
+
+            try {
+                String pyStr;
+
+                mSplStart = mIPinyinDecoderService.imGetSplStart();
+                pyStr = mIPinyinDecoderService.imGetPyStr(false);
+                mSurfaceDecodedLen = mIPinyinDecoderService.imGetPyStrLen(true);
+                assert (mSurfaceDecodedLen <= pyStr.length());
+
+                mFullSent = mIPinyinDecoderService.imGetChoice(0);
+                mFixedLen = mIPinyinDecoderService.imGetFixedLen();
+
+                // Update the surface string to the one kept by engine.
+                mSurface.replace(0, mSurface.length(), pyStr);
+
+                if (mCursorPos > mSurface.length())
+                    mCursorPos = mSurface.length();
+                mComposingStr = mFullSent.substring(0, mFixedLen)
+                        + mSurface.substring(mSplStart[mFixedLen + 1]);
+
+                mActiveCmpsLen = mComposingStr.length();
+                if (mSurfaceDecodedLen > 0) {
+                    mActiveCmpsLen = mActiveCmpsLen
+                            - (mSurface.length() - mSurfaceDecodedLen);
+                }
+
+                // Prepare the display string.
+                if (0 == mSurfaceDecodedLen) {
+                    mComposingStrDisplay = mComposingStr;
+                    mActiveCmpsDisplayLen = mComposingStr.length();
+                } else {
+                    mComposingStrDisplay = mFullSent.substring(0, mFixedLen);
+                    for (int pos = mFixedLen + 1; pos < mSplStart.length - 1; pos++) {
+                        mComposingStrDisplay += mSurface.substring(
+                                mSplStart[pos], mSplStart[pos + 1]);
+                        if (mSplStart[pos + 1] < mSurfaceDecodedLen) {
+                            mComposingStrDisplay += " ";
+                        }
+                    }
+                    mActiveCmpsDisplayLen = mComposingStrDisplay.length();
+                    if (mSurfaceDecodedLen < mSurface.length()) {
+                        mComposingStrDisplay += mSurface
+                                .substring(mSurfaceDecodedLen);
+                    }
+                }
+
+                if (mSplStart.length == mFixedLen + 2) {
+                    mFinishSelection = true;
+                } else {
+                    mFinishSelection = false;
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, "PinyinDecoderService died", e);
+            }
+            // Prepare page 0.
+            if (!mFinishSelection) {
+                preparePage(0);
+            }
+        }
+
+        private void choosePredictChoice(int choiceId) {
+            if (ImeState.STATE_PREDICT != mImeState || choiceId < 0
+                    || choiceId >= mTotalChoicesNum) {
+                return;
+            }
+
+            String tmp = mCandidatesList.get(choiceId);
+
+            resetCandidates();
+
+            mCandidatesList.add(tmp);
+            mTotalChoicesNum = 1;
+
+            mSurface.replace(0, mSurface.length(), "");
+            mCursorPos = 0;
+            mFullSent = tmp;
+            mFixedLen = tmp.length();
+            mComposingStr = mFullSent;
+            mActiveCmpsLen = mFixedLen;
+
+            mFinishSelection = true;
+        }
+
+        public String getCandidate(int candId) {
+            // Only loaded items can be gotten, so we use mCandidatesList.size()
+            // instead mTotalChoiceNum.
+            if (candId < 0 || candId > mCandidatesList.size()) {
+                return null;
+            }
+            return mCandidatesList.get(candId);
+        }
+
+        private void getCandiagtesForCache() {
+            int fetchStart = mCandidatesList.size();
+            int fetchSize = mTotalChoicesNum - fetchStart;
+            if (fetchSize > MAX_PAGE_SIZE_DISPLAY) {
+                fetchSize = MAX_PAGE_SIZE_DISPLAY;
+            }
+            try {
+                List<String> newList = null;
+                if (ImeState.STATE_INPUT == mImeState ||
+                        ImeState.STATE_IDLE == mImeState ||
+                        ImeState.STATE_COMPOSING == mImeState){
+                    newList = mIPinyinDecoderService.imGetChoiceList(
+                            fetchStart, fetchSize, mFixedLen);
+                } else if (ImeState.STATE_PREDICT == mImeState) {
+                    newList = mIPinyinDecoderService.imGetPredictList(
+                            fetchStart, fetchSize);
+                } else if (ImeState.STATE_APP_COMPLETION == mImeState) {
+                    newList = new ArrayList<String>();
+                    if (null != mAppCompletions) {
+                        for (int pos = fetchStart; pos < fetchSize; pos++) {
+                            CompletionInfo ci = mAppCompletions[pos];
+                            if (null != ci) {
+                                CharSequence s = ci.getText();
+                                if (null != s) newList.add(s.toString());
+                            }
+                        }
+                    }
+                }
+                mCandidatesList.addAll(newList);
+            } catch (RemoteException e) {
+                Log.w(TAG, "PinyinDecoderService died", e);
+            }
+        }
+
+        public boolean pageReady(int pageNo) {
+            // If the page number is less than 0, return false
+            if (pageNo < 0) return false;
+
+            // Page pageNo's ending information is not ready.
+            if (mPageStart.size() <= pageNo + 1) {
+                return false;
+            }
+
+            return true;
+        }
+
+        public boolean preparePage(int pageNo) {
+            // If the page number is less than 0, return false
+            if (pageNo < 0) return false;
+
+            // Make sure the starting information for page pageNo is ready.
+            if (mPageStart.size() <= pageNo) {
+                return false;
+            }
+
+            // Page pageNo's ending information is also ready.
+            if (mPageStart.size() > pageNo + 1) {
+                return true;
+            }
+
+            // If cached items is enough for page pageNo.
+            if (mCandidatesList.size() - mPageStart.elementAt(pageNo) >= MAX_PAGE_SIZE_DISPLAY) {
+                return true;
+            }
+
+            // Try to get more items from engine
+            getCandiagtesForCache();
+
+            // Try to find if there are available new items to display.
+            // If no new item, return false;
+            if (mPageStart.elementAt(pageNo) >= mCandidatesList.size()) {
+                return false;
+            }
+
+            // If there are new items, return true;
+            return true;
+        }
+
+        public void preparePredicts(CharSequence history) {
+            if (null == history) return;
+
+            resetCandidates();
+
+            if (Settings.getPrediction()) {
+                String preEdit = history.toString();
+                int predictNum = 0;
+                if (null != preEdit) {
+                    try {
+                        mTotalChoicesNum = mIPinyinDecoderService
+                                .imGetPredictsNum(preEdit);
+                    } catch (RemoteException e) {
+                        return;
+                    }
+                }
+            }
+
+            preparePage(0);
+            mFinishSelection = false;
+        }
+
+        private void prepareAppCompletions(CompletionInfo completions[]) {
+            resetCandidates();
+            mAppCompletions = completions;
+            mTotalChoicesNum = completions.length;
+            preparePage(0);
+            mFinishSelection = false;
+            return;
+        }
+
+        public int getCurrentPageSize(int currentPage) {
+            if (mPageStart.size() <= currentPage + 1) return 0;
+            return mPageStart.elementAt(currentPage + 1)
+                    - mPageStart.elementAt(currentPage);
+        }
+
+        public int getCurrentPageStart(int currentPage) {
+            if (mPageStart.size() < currentPage + 1) return mTotalChoicesNum;
+            return mPageStart.elementAt(currentPage);
+        }
+
+        public boolean pageForwardable(int currentPage) {
+            if (mPageStart.size() <= currentPage + 1) return false;
+            if (mPageStart.elementAt(currentPage + 1) >= mTotalChoicesNum) {
+                return false;
+            }
+            return true;
+        }
+
+        public boolean pageBackwardable(int currentPage) {
+            if (currentPage > 0) return true;
+            return false;
+        }
+
+        public boolean charBeforeCursorIsSeparator() {
+            int len = mSurface.length();
+            if (mCursorPos > len) return false;
+            if (mCursorPos > 0 && mSurface.charAt(mCursorPos - 1) == '\'') {
+                return true;
+            }
+            return false;
+        }
+
+        public int getCursorPos() {
+            return mCursorPos;
+        }
+
+        public int getCursorPosInCmps() {
+            int cursorPos = mCursorPos;
+            int fixedLen = 0;
+
+            for (int hzPos = 0; hzPos < mFixedLen; hzPos++) {
+                if (mCursorPos >= mSplStart[hzPos + 2]) {
+                    cursorPos -= mSplStart[hzPos + 2] - mSplStart[hzPos + 1];
+                    cursorPos += 1;
+                }
+            }
+            return cursorPos;
+        }
+
+        public int getCursorPosInCmpsDisplay() {
+            int cursorPos = getCursorPosInCmps();
+            // +2 is because: one for mSplStart[0], which is used for other
+            // purpose(The length of the segmentation string), and another
+            // for the first spelling which does not need a space before it.
+            for (int pos = mFixedLen + 2; pos < mSplStart.length - 1; pos++) {
+                if (mCursorPos <= mSplStart[pos]) {
+                    break;
+                } else {
+                    cursorPos++;
+                }
+            }
+            return cursorPos;
+        }
+
+        public void moveCursorToEdge(boolean left) {
+            if (left)
+                mCursorPos = 0;
+            else
+                mCursorPos = mSurface.length();
+        }
+
+        // Move cursor. If offset is 0, this function can be used to adjust
+        // the cursor into the bounds of the string.
+        public void moveCursor(int offset) {
+            if (offset > 1 || offset < -1) return;
+
+            if (offset != 0) {
+                int hzPos = 0;
+                for (hzPos = 0; hzPos <= mFixedLen; hzPos++) {
+                    if (mCursorPos == mSplStart[hzPos + 1]) {
+                        if (offset < 0) {
+                            if (hzPos > 0) {
+                                offset = mSplStart[hzPos]
+                                        - mSplStart[hzPos + 1];
+                            }
+                        } else {
+                            if (hzPos < mFixedLen) {
+                                offset = mSplStart[hzPos + 2]
+                                        - mSplStart[hzPos + 1];
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+            mCursorPos += offset;
+            if (mCursorPos < 0) {
+                mCursorPos = 0;
+            } else if (mCursorPos > mSurface.length()) {
+                mCursorPos = mSurface.length();
+            }
+        }
+
+        public int getSplNum() {
+            return mSplStart[0];
+        }
+
+        public int getFixedLen() {
+            return mFixedLen;
+        }
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/Settings.java b/PinyinIME/src/com/android/inputmethod/pinyin/Settings.java
new file mode 100644
index 0000000..c05f605
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/Settings.java
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+
+/**
+ * Class used to maintain settings.
+ */
+public class Settings {
+    private static final String ANDPY_CONFS_KEYSOUND_KEY = "Sound";
+    private static final String ANDPY_CONFS_VIBRATE_KEY = "Vibrate";
+    private static final String ANDPY_CONFS_PREDICTION_KEY = "Prediction";
+    
+    private static boolean mKeySound;
+    private static boolean mVibrate;
+    private static boolean mPrediction;
+    
+    private static Settings mInstance = null;
+
+    private static int mRefCount = 0;
+
+    private static SharedPreferences mSharedPref = null;
+
+    protected Settings(SharedPreferences pref) {
+        mSharedPref = pref;
+        initConfs();
+    }
+
+    public static Settings getInstance(SharedPreferences pref) {
+        if (mInstance == null) {
+            mInstance = new Settings(pref);
+        }
+        assert (pref == mSharedPref);
+        mRefCount++;
+        return mInstance;
+    }
+
+    public static void writeBack() {
+        Editor editor = mSharedPref.edit();
+        editor.putBoolean(ANDPY_CONFS_VIBRATE_KEY, mVibrate);
+        editor.putBoolean(ANDPY_CONFS_KEYSOUND_KEY, mKeySound);
+        editor.putBoolean(ANDPY_CONFS_PREDICTION_KEY, mPrediction);
+        editor.commit();
+    }
+
+    public static void releaseInstance() {
+        mRefCount--;
+        if (mRefCount == 0) {
+            mInstance = null;
+        }
+    }
+
+    private void initConfs() {
+        mKeySound = mSharedPref.getBoolean(ANDPY_CONFS_KEYSOUND_KEY, true);
+        mVibrate = mSharedPref.getBoolean(ANDPY_CONFS_VIBRATE_KEY, false);
+        mPrediction = mSharedPref.getBoolean(ANDPY_CONFS_PREDICTION_KEY, true);
+    }
+
+    public static boolean getKeySound() {
+        return mKeySound;
+    }
+
+    public static void setKeySound(boolean v) {
+        if (mKeySound == v) return;
+        mKeySound = v;
+    }
+
+    public static boolean getVibrate() {
+        return mVibrate;
+    }
+
+    public static void setVibrate(boolean v) {
+        if (mVibrate == v) return;
+        mVibrate = v;
+    }
+
+    public static boolean getPrediction() {
+        return mPrediction;
+    }
+
+    public static void setPrediction(boolean v) {
+        if (mPrediction == v) return;
+        mPrediction = v;
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SettingsActivity.java b/PinyinIME/src/com/android/inputmethod/pinyin/SettingsActivity.java
new file mode 100644
index 0000000..7d23d8e
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/SettingsActivity.java
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import java.util.List;
+
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import com.android.inputmethod.pinyin.Settings;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+/**
+ * Setting activity of Pinyin IME.
+ */
+public class SettingsActivity extends PreferenceActivity implements
+        Preference.OnPreferenceChangeListener {
+
+    private static String TAG = "SettingsActivity";
+
+    private CheckBoxPreference mKeySoundPref;
+    private CheckBoxPreference mVibratePref;
+    private CheckBoxPreference mPredictionPref;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.settings);
+
+        PreferenceScreen prefSet = getPreferenceScreen();
+
+        mKeySoundPref = (CheckBoxPreference) prefSet
+                .findPreference(getString(R.string.setting_sound_key));
+        mVibratePref = (CheckBoxPreference) prefSet
+                .findPreference(getString(R.string.setting_vibrate_key));
+        mPredictionPref = (CheckBoxPreference) prefSet
+                .findPreference(getString(R.string.setting_prediction_key));
+        
+        prefSet.setOnPreferenceChangeListener(this);
+        
+        Settings.getInstance(PreferenceManager
+                .getDefaultSharedPreferences(getApplicationContext()));
+
+        updatePreference(prefSet, getString(R.string.setting_advanced_key));
+        
+        updateWidgets();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        updateWidgets();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Settings.releaseInstance();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        Settings.setKeySound(mKeySoundPref.isChecked());
+        Settings.setVibrate(mVibratePref.isChecked());
+        Settings.setPrediction(mPredictionPref.isChecked());
+
+        Settings.writeBack();
+    }
+
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        return true;
+    }
+
+    private void updateWidgets() {
+        mKeySoundPref.setChecked(Settings.getKeySound());
+        mVibratePref.setChecked(Settings.getVibrate());
+        mPredictionPref.setChecked(Settings.getPrediction());
+    }
+
+    public void updatePreference(PreferenceGroup parentPref, String prefKey) {
+        Preference preference = parentPref.findPreference(prefKey);
+        if (preference == null) {
+            return;
+        }
+        Intent intent = preference.getIntent();
+        if (intent != null) {
+            PackageManager pm = getPackageManager();
+            List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+            int listSize = list.size();
+            if (listSize == 0)
+                parentPref.removePreference(preference);
+        }
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SkbContainer.java b/PinyinIME/src/com/android/inputmethod/pinyin/SkbContainer.java
new file mode 100644
index 0000000..2294860
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/SkbContainer.java
@@ -0,0 +1,642 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.inputmethodservice.InputMethodService;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.PopupWindow;
+import android.widget.RelativeLayout;
+import android.widget.ViewFlipper;
+
+/**
+ * The top container to host soft keyboard view(s).
+ */
+public class SkbContainer extends RelativeLayout implements OnTouchListener {
+    /**
+     * For finger touch, user tends to press the bottom part of the target key,
+     * or he/she even presses the area out of it, so it is necessary to make a
+     * simple bias correction. If the input method runs on emulator, no bias
+     * correction will be used.
+     */
+    private static final int Y_BIAS_CORRECTION = -10;
+
+    /**
+     * Used to skip these move events whose position is too close to the
+     * previous touch events.
+     */
+    private static final int MOVE_TOLERANCE = 6;
+
+    /**
+     * If this member is true, PopupWindow is used to show on-key highlight
+     * effect.
+     */
+    private static boolean POPUPWINDOW_FOR_PRESSED_UI = false;
+
+    /**
+     * The current soft keyboard layout.
+     * 
+     * @see com.android.inputmethod.pinyin.InputModeSwitcher for detailed layout
+     *      definitions.
+     */
+    private int mSkbLayout = 0;
+
+    /**
+     * The input method service.
+     */
+    private InputMethodService mService;
+
+    /**
+     * Input mode switcher used to switch between different modes like Chinese,
+     * English, etc.
+     */
+    private InputModeSwitcher mInputModeSwitcher;
+
+    /**
+     * The gesture detector.
+     */
+    private GestureDetector mGestureDetector;
+
+    private Environment mEnvironment;
+
+    private ViewFlipper mSkbFlipper;
+
+    /**
+     * The popup balloon hint for key press/release.
+     */
+    private BalloonHint mBalloonPopup;
+
+    /**
+     * The on-key balloon hint for key press/release.
+     */
+    private BalloonHint mBalloonOnKey = null;
+
+    /** The major sub soft keyboard. */
+    private SoftKeyboardView mMajorView;
+
+    /**
+     * The last parameter when function {@link #toggleCandidateMode(boolean)}
+     * was called.
+     */
+    private boolean mLastCandidatesShowing;
+
+    /** Used to indicate whether a popup soft keyboard is shown. */
+    private boolean mPopupSkbShow = false;
+
+    /**
+     * Used to indicate whether a popup soft keyboard is just shown, and waits
+     * for the touch event to release. After the release, the popup window can
+     * response to touch events.
+     **/
+    private boolean mPopupSkbNoResponse = false;
+
+    /** Popup sub keyboard. */
+    private PopupWindow mPopupSkb;
+
+    /** The view of the popup sub soft keyboard. */
+    private SoftKeyboardView mPopupSkbView;
+
+    private int mPopupX;
+
+    private int mPopupY;
+
+    /**
+     * When user presses a key, a timer is started, when it times out, it is
+     * necessary to detect whether user still holds the key.
+     */
+    private volatile boolean mWaitForTouchUp = false;
+
+    /**
+     * When user drags on the soft keyboard and the distance is enough, this
+     * drag will be recognized as a gesture and a gesture-based action will be
+     * taken, in this situation, ignore the consequent events.
+     */
+    private volatile boolean mDiscardEvent = false;
+
+    /**
+     * For finger touch, user tends to press the bottom part of the target key,
+     * or he/she even presses the area out of it, so it is necessary to make a
+     * simple bias correction in Y.
+     */
+    private int mYBiasCorrection = 0;
+
+    /**
+     * The x coordination of the last touch event.
+     */
+    private int mXLast;
+
+    /**
+     * The y coordination of the last touch event.
+     */
+    private int mYLast;
+
+    /**
+     * The soft keyboard view.
+     */
+    private SoftKeyboardView mSkv;
+
+    /**
+     * The position of the soft keyboard view in the container.
+     */
+    private int mSkvPosInContainer[] = new int[2];
+
+    /**
+     * The key pressed by user.
+     */
+    private SoftKey mSoftKeyDown = null;
+
+    /**
+     * Used to timeout a press if user holds the key for a long time.
+     */
+    private LongPressTimer mLongPressTimer;
+
+    /**
+     * For temporary use.
+     */
+    private int mXyPosTmp[] = new int[2];
+
+    public SkbContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mEnvironment = Environment.getInstance();
+
+        mLongPressTimer = new LongPressTimer(this);
+
+        // If it runs on an emulator, no bias correction
+        if ("1".equals(SystemProperties.get("ro.kernel.qemu"))) {
+            mYBiasCorrection = 0;
+        } else {
+            mYBiasCorrection = Y_BIAS_CORRECTION;
+        }
+        mBalloonPopup = new BalloonHint(context, this, MeasureSpec.AT_MOST);
+        if (POPUPWINDOW_FOR_PRESSED_UI) {
+            mBalloonOnKey = new BalloonHint(context, this, MeasureSpec.AT_MOST);
+        }
+
+        mPopupSkb = new PopupWindow(mContext);
+        mPopupSkb.setBackgroundDrawable(null);
+        mPopupSkb.setClippingEnabled(false);
+    }
+
+    public void setService(InputMethodService service) {
+        mService = service;
+    }
+
+    public void setInputModeSwitcher(InputModeSwitcher inputModeSwitcher) {
+        mInputModeSwitcher = inputModeSwitcher;
+    }
+
+    public void setGestureDetector(GestureDetector gestureDetector) {
+        mGestureDetector = gestureDetector;
+    }
+
+    public boolean isCurrentSkbSticky() {
+        if (null == mMajorView) return true;
+        SoftKeyboard skb = mMajorView.getSoftKeyboard();
+        if (null != skb) {
+            return skb.getStickyFlag();
+        }
+        return true;
+    }
+
+    public void toggleCandidateMode(boolean candidatesShowing) {
+        if (null == mMajorView || !mInputModeSwitcher.isChineseText()
+                || mLastCandidatesShowing == candidatesShowing) return;
+        mLastCandidatesShowing = candidatesShowing;
+
+        SoftKeyboard skb = mMajorView.getSoftKeyboard();
+        if (null == skb) return;
+
+        int state = mInputModeSwitcher.getTooggleStateForCnCand();
+        if (!candidatesShowing) {
+            skb.disableToggleState(state, false);
+            skb.enableToggleStates(mInputModeSwitcher.getToggleStates());
+        } else {
+            skb.enableToggleState(state, false);
+        }
+
+        mMajorView.invalidate();
+    }
+
+    public void updateInputMode() {
+        int skbLayout = mInputModeSwitcher.getSkbLayout();
+        if (mSkbLayout != skbLayout) {
+            mSkbLayout = skbLayout;
+            updateSkbLayout();
+        }
+
+        mLastCandidatesShowing = false;
+
+        if (null == mMajorView) return;
+
+        SoftKeyboard skb = mMajorView.getSoftKeyboard();
+        if (null == skb) return;
+        skb.enableToggleStates(mInputModeSwitcher.getToggleStates());
+        invalidate();
+        return;
+    }
+
+    private void updateSkbLayout() {
+        int screenWidth = mEnvironment.getScreenWidth();
+        int keyHeight = mEnvironment.getKeyHeight();
+        int skbHeight = mEnvironment.getSkbHeight();
+
+        Resources r = mContext.getResources();
+        if (null == mSkbFlipper) {
+            mSkbFlipper = (ViewFlipper) findViewById(R.id.alpha_floatable);
+        }
+        mMajorView = (SoftKeyboardView) mSkbFlipper.getChildAt(0);
+
+        SoftKeyboard majorSkb = null;
+        SkbPool skbPool = SkbPool.getInstance();
+
+        switch (mSkbLayout) {
+        case R.xml.skb_qwerty:
+            majorSkb = skbPool.getSoftKeyboard(R.xml.skb_qwerty,
+                    R.xml.skb_qwerty, screenWidth, skbHeight, mContext);
+            break;
+
+        case R.xml.skb_sym1:
+            majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym1, R.xml.skb_sym1,
+                    screenWidth, skbHeight, mContext);
+            break;
+
+        case R.xml.skb_sym2:
+            majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym2, R.xml.skb_sym2,
+                    screenWidth, skbHeight, mContext);
+            break;
+
+        case R.xml.skb_smiley:
+            majorSkb = skbPool.getSoftKeyboard(R.xml.skb_smiley,
+                    R.xml.skb_smiley, screenWidth, skbHeight, mContext);
+            break;
+
+        case R.xml.skb_phone:
+            majorSkb = skbPool.getSoftKeyboard(R.xml.skb_phone,
+                    R.xml.skb_phone, screenWidth, skbHeight, mContext);
+            break;
+        default:
+        }
+
+        if (null == majorSkb || !mMajorView.setSoftKeyboard(majorSkb)) {
+            return;
+        }
+        mMajorView.setBalloonHint(mBalloonOnKey, mBalloonPopup, false);
+        mMajorView.invalidate();
+    }
+
+    private void responseKeyEvent(SoftKey sKey) {
+        if (null == sKey) return;
+        ((PinyinIME) mService).responseSoftKeyEvent(sKey);
+        return;
+    }
+
+    private SoftKeyboardView inKeyboardView(int x, int y,
+            int positionInParent[]) {
+        if (mPopupSkbShow) {
+            if (mPopupX <= x && mPopupX + mPopupSkb.getWidth() > x
+                    && mPopupY <= y && mPopupY + mPopupSkb.getHeight() > y) {
+                positionInParent[0] = mPopupX;
+                positionInParent[1] = mPopupY;
+                mPopupSkbView.setOffsetToSkbContainer(positionInParent);
+                return mPopupSkbView;
+            }
+            return null;
+        }
+
+        return mMajorView;
+    }
+
+    private void popupSymbols() {
+        int popupResId = mSoftKeyDown.getPopupResId();
+        if (popupResId > 0) {
+            int skbContainerWidth = getWidth();
+            int skbContainerHeight = getHeight();
+            // The paddings of the background are not included.
+            int miniSkbWidth = (int) (skbContainerWidth * 0.8);
+            int miniSkbHeight = (int) (skbContainerHeight * 0.23);
+
+            SkbPool skbPool = SkbPool.getInstance();
+            SoftKeyboard skb = skbPool.getSoftKeyboard(popupResId, popupResId,
+                    miniSkbWidth, miniSkbHeight, mContext);
+            if (null == skb) return;
+
+            mPopupX = (skbContainerWidth - skb.getSkbTotalWidth()) / 2;
+            mPopupY = (skbContainerHeight - skb.getSkbTotalHeight()) / 2;
+
+            if (null == mPopupSkbView) {
+                mPopupSkbView = new SoftKeyboardView(mContext, null);
+                mPopupSkbView.onMeasure(LayoutParams.WRAP_CONTENT,
+                        LayoutParams.WRAP_CONTENT);
+            }
+            mPopupSkbView.setOnTouchListener(this);
+            mPopupSkbView.setSoftKeyboard(skb);
+            mPopupSkbView.setBalloonHint(mBalloonOnKey, mBalloonPopup, true);
+
+            mPopupSkb.setContentView(mPopupSkbView);
+            mPopupSkb.setWidth(skb.getSkbCoreWidth()
+                    + mPopupSkbView.getPaddingLeft()
+                    + mPopupSkbView.getPaddingRight());
+            mPopupSkb.setHeight(skb.getSkbCoreHeight()
+                    + mPopupSkbView.getPaddingTop()
+                    + mPopupSkbView.getPaddingBottom());
+
+            getLocationInWindow(mXyPosTmp);
+            mPopupSkb.showAtLocation(this, Gravity.NO_GRAVITY, mPopupX, mPopupY
+                    + mXyPosTmp[1]);
+            mPopupSkbShow = true;
+            mPopupSkbNoResponse = true;
+            // Invalidate itself to dim the current soft keyboards.
+            dimSoftKeyboard(true);
+            resetKeyPress(0);
+        }
+    }
+
+    private void dimSoftKeyboard(boolean dimSkb) {
+        mMajorView.dimSoftKeyboard(dimSkb);
+    }
+
+    private void dismissPopupSkb() {
+        mPopupSkb.dismiss();
+        mPopupSkbShow = false;
+        dimSoftKeyboard(false);
+        resetKeyPress(0);
+    }
+
+    private void resetKeyPress(long delay) {
+        mLongPressTimer.removeTimer();
+
+        if (null != mSkv) {
+            mSkv.resetKeyPress(delay);
+        }
+    }
+
+    public boolean handleBack(boolean realAction) {
+        if (mPopupSkbShow) {
+            if (!realAction) return true;
+
+            dismissPopupSkb();
+            mDiscardEvent = true;
+            return true;
+        }
+        return false;
+    }
+
+    public void dismissPopups() {
+        handleBack(true);
+        resetKeyPress(0);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Environment env = Environment.getInstance();
+        int measuredWidth = env.getScreenWidth();
+        int measuredHeight = getPaddingTop();
+        measuredHeight += env.getSkbHeight();
+        widthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth,
+                MeasureSpec.EXACTLY);
+        heightMeasureSpec = MeasureSpec.makeMeasureSpec(measuredHeight,
+                MeasureSpec.EXACTLY);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        if (mSkbFlipper.isFlipping()) {
+            resetKeyPress(0);
+            return true;
+        }
+
+        int x = (int) event.getX();
+        int y = (int) event.getY();
+        // Bias correction
+        y = y + mYBiasCorrection;
+
+        // Ignore short-distance movement event to get better performance.
+        if (event.getAction() == MotionEvent.ACTION_MOVE) {
+            if (Math.abs(x - mXLast) <= MOVE_TOLERANCE
+                    && Math.abs(y - mYLast) <= MOVE_TOLERANCE) {
+                return true;
+            }
+        }
+
+        mXLast = x;
+        mYLast = y;
+
+        if (!mPopupSkbShow) {
+            if (mGestureDetector.onTouchEvent(event)) {
+                resetKeyPress(0);
+                mDiscardEvent = true;
+                return true;
+            }
+        }
+
+        switch (event.getAction()) {
+        case MotionEvent.ACTION_DOWN:
+            resetKeyPress(0);
+
+            mWaitForTouchUp = true;
+            mDiscardEvent = false;
+
+            mSkv = null;
+            mSoftKeyDown = null;
+            mSkv = inKeyboardView(x, y, mSkvPosInContainer);
+            if (null != mSkv) {
+                mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y
+                        - mSkvPosInContainer[1], mLongPressTimer, false);
+            }
+            break;
+
+        case MotionEvent.ACTION_MOVE:
+            if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
+                break;
+            }
+            if (mDiscardEvent) {
+                resetKeyPress(0);
+                break;
+            }
+
+            if (mPopupSkbShow && mPopupSkbNoResponse) {
+                break;
+            }
+
+            SoftKeyboardView skv = inKeyboardView(x, y, mSkvPosInContainer);
+            if (null != skv) {
+                if (skv != mSkv) {
+                    mSkv = skv;
+                    mSoftKeyDown = mSkv.onKeyPress(x - mSkvPosInContainer[0], y
+                            - mSkvPosInContainer[1], mLongPressTimer, true);
+                } else if (null != skv) {
+                    if (null != mSkv) {
+                        mSoftKeyDown = mSkv.onKeyMove(
+                                x - mSkvPosInContainer[0], y
+                                        - mSkvPosInContainer[1]);
+                        if (null == mSoftKeyDown) {
+                            mDiscardEvent = true;
+                        }
+                    }
+                }
+            }
+            break;
+
+        case MotionEvent.ACTION_UP:
+            if (mDiscardEvent) {
+                resetKeyPress(0);
+                break;
+            }
+
+            mWaitForTouchUp = false;
+
+            // The view which got the {@link MotionEvent#ACTION_DOWN} event is
+            // always used to handle this event.
+            if (null != mSkv) {
+                mSkv.onKeyRelease(x - mSkvPosInContainer[0], y
+                        - mSkvPosInContainer[1]);
+            }
+
+            if (!mPopupSkbShow || !mPopupSkbNoResponse) {
+                responseKeyEvent(mSoftKeyDown);
+            }
+
+            if (mSkv == mPopupSkbView && !mPopupSkbNoResponse) {
+                dismissPopupSkb();
+            }
+            mPopupSkbNoResponse = false;
+            break;
+
+        case MotionEvent.ACTION_CANCEL:
+            break;
+        }
+
+        if (null == mSkv) {
+            return false;
+        }
+
+        return true;
+    }
+
+    // Function for interface OnTouchListener, it is used to handle touch events
+    // which will be delivered to the popup soft keyboard view.
+    public boolean onTouch(View v, MotionEvent event) {
+        // Translate the event to fit to the container.
+        MotionEvent newEv = MotionEvent.obtain(event.getDownTime(), event
+                .getEventTime(), event.getAction(), event.getX() + mPopupX,
+                event.getY() + mPopupY, event.getPressure(), event.getSize(),
+                event.getMetaState(), event.getXPrecision(), event
+                        .getYPrecision(), event.getDeviceId(), event
+                        .getEdgeFlags());
+        boolean ret = onTouchEvent(newEv);
+        return ret;
+    }
+
+    class LongPressTimer extends Handler implements Runnable {
+        /**
+         * When user presses a key for a long time, the timeout interval to
+         * generate first {@link #LONG_PRESS_KEYNUM1} key events.
+         */
+        public static final int LONG_PRESS_TIMEOUT1 = 500;
+
+        /**
+         * When user presses a key for a long time, after the first
+         * {@link #LONG_PRESS_KEYNUM1} key events, this timeout interval will be
+         * used.
+         */
+        private static final int LONG_PRESS_TIMEOUT2 = 100;
+
+        /**
+         * When user presses a key for a long time, after the first
+         * {@link #LONG_PRESS_KEYNUM2} key events, this timeout interval will be
+         * used.
+         */
+        private static final int LONG_PRESS_TIMEOUT3 = 100;
+
+        /**
+         * When user presses a key for a long time, after the first
+         * {@link #LONG_PRESS_KEYNUM1} key events, timeout interval
+         * {@link #LONG_PRESS_TIMEOUT2} will be used instead.
+         */
+        public static final int LONG_PRESS_KEYNUM1 = 1;
+
+        /**
+         * When user presses a key for a long time, after the first
+         * {@link #LONG_PRESS_KEYNUM2} key events, timeout interval
+         * {@link #LONG_PRESS_TIMEOUT3} will be used instead.
+         */
+        public static final int LONG_PRESS_KEYNUM2 = 3;
+
+        SkbContainer mSkbContainer;
+
+        private int mResponseTimes = 0;
+
+        public LongPressTimer(SkbContainer skbContainer) {
+            mSkbContainer = skbContainer;
+        }
+
+        public void startTimer() {
+            postAtTime(this, SystemClock.uptimeMillis() + LONG_PRESS_TIMEOUT1);
+            mResponseTimes = 0;
+        }
+
+        public boolean removeTimer() {
+            removeCallbacks(this);
+            return true;
+        }
+
+        public void run() {
+            if (mWaitForTouchUp) {
+                mResponseTimes++;
+                if (mSoftKeyDown.repeatable()) {
+                    if (mSoftKeyDown.isUserDefKey()) {
+                        if (1 == mResponseTimes) {
+                            if (mInputModeSwitcher
+                                    .tryHandleLongPressSwitch(mSoftKeyDown.mKeyCode)) {
+                                mDiscardEvent = true;
+                                resetKeyPress(0);
+                            }
+                        }
+                    } else {
+                        responseKeyEvent(mSoftKeyDown);
+                        long timeout;
+                        if (mResponseTimes < LONG_PRESS_KEYNUM1) {
+                            timeout = LONG_PRESS_TIMEOUT1;
+                        } else if (mResponseTimes < LONG_PRESS_KEYNUM2) {
+                            timeout = LONG_PRESS_TIMEOUT2;
+                        } else {
+                            timeout = LONG_PRESS_TIMEOUT3;
+                        }
+                        postAtTime(this, SystemClock.uptimeMillis() + timeout);
+                    }
+                } else {
+                    if (1 == mResponseTimes) {
+                        popupSymbols();
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SkbPool.java b/PinyinIME/src/com/android/inputmethod/pinyin/SkbPool.java
new file mode 100644
index 0000000..4c46951
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/SkbPool.java
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import java.util.Vector;
+
+import android.content.Context;
+
+/**
+ * Class used to cache previously loaded soft keyboard layouts.
+ */
+public class SkbPool {
+    private static SkbPool mInstance = null;
+
+    private Vector<SkbTemplate> mSkbTemplates = new Vector<SkbTemplate>();
+    private Vector<SoftKeyboard> mSoftKeyboards = new Vector<SoftKeyboard>();
+
+    private SkbPool() {
+    }
+
+    public static SkbPool getInstance() {
+        if (null == mInstance) mInstance = new SkbPool();
+        return mInstance;
+    }
+
+    public void resetCachedSkb() {
+        mSoftKeyboards.clear();
+    }
+
+    public SkbTemplate getSkbTemplate(int skbTemplateId, Context context) {
+        for (int i = 0; i < mSkbTemplates.size(); i++) {
+            SkbTemplate t = mSkbTemplates.elementAt(i);
+            if (t.getSkbTemplateId() == skbTemplateId) {
+                return t;
+            }
+        }
+
+        if (null != context) {
+            XmlKeyboardLoader xkbl = new XmlKeyboardLoader(context);
+            SkbTemplate t = xkbl.loadSkbTemplate(skbTemplateId);
+            if (null != t) {
+                mSkbTemplates.add(t);
+                return t;
+            }
+        }
+        return null;
+    }
+
+    // Try to find the keyboard in the pool with the cache id. If there is no
+    // keyboard found, try to load it with the given xml id.
+    public SoftKeyboard getSoftKeyboard(int skbCacheId, int skbXmlId,
+            int skbWidth, int skbHeight, Context context) {
+        for (int i = 0; i < mSoftKeyboards.size(); i++) {
+            SoftKeyboard skb = mSoftKeyboards.elementAt(i);
+            if (skb.getCacheId() == skbCacheId && skb.getSkbXmlId() == skbXmlId) {
+                skb.setSkbCoreSize(skbWidth, skbHeight);
+                skb.setNewlyLoadedFlag(false);
+                return skb;
+            }
+        }
+        if (null != context) {
+            XmlKeyboardLoader xkbl = new XmlKeyboardLoader(context);
+            SoftKeyboard skb = xkbl.loadKeyboard(skbXmlId, skbWidth, skbHeight);
+            if (skb != null) {
+                if (skb.getCacheFlag()) {
+                    skb.setCacheId(skbCacheId);
+                    mSoftKeyboards.add(skb);
+                }
+            }
+            return skb;
+        }
+        return null;
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SkbTemplate.java b/PinyinIME/src/com/android/inputmethod/pinyin/SkbTemplate.java
new file mode 100644
index 0000000..9ab53ff
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/SkbTemplate.java
@@ -0,0 +1,233 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.graphics.drawable.Drawable;
+
+import java.util.Vector;
+
+/**
+ * Key icon definition. It is defined in soft keyboard template. A soft keyboard
+ * can refer to such an icon in its xml file directly to improve performance.
+ */
+class KeyIconRecord {
+    int keyCode;
+    Drawable icon;
+    Drawable iconPopup;
+}
+
+
+/**
+ * Default definition for a certain key. It is defined in soft keyboard
+ * template. A soft keyboard can refer to a default key in its xml file. Nothing
+ * of the key can be overwritten, including the size.
+ */
+class KeyRecord {
+    int keyId;
+    SoftKey softKey;
+}
+
+
+/**
+ * Soft keyboard template used by soft keyboards to share common resources. In
+ * this way, memory cost is reduced.
+ */
+public class SkbTemplate {
+    private int mSkbTemplateId;
+    private Drawable mSkbBg;
+    private Drawable mBalloonBg;
+    private Drawable mPopupBg;
+    private float mXMargin = 0;
+    private float mYMargin = 0;
+    /** Key type list. */
+    private Vector<SoftKeyType> mKeyTypeList = new Vector<SoftKeyType>();
+
+    /**
+     * Default key icon list. It is only for keys which do not have popup icons.
+     */
+    private Vector<KeyIconRecord> mKeyIconRecords = new Vector<KeyIconRecord>();
+
+    /**
+     * Default key list.
+     */
+    private Vector<KeyRecord> mKeyRecords = new Vector<KeyRecord>();
+
+    public SkbTemplate(int skbTemplateId) {
+        mSkbTemplateId = skbTemplateId;
+    }
+
+    public int getSkbTemplateId() {
+        return mSkbTemplateId;
+    }
+
+    public void setBackgrounds(Drawable skbBg, Drawable balloonBg,
+            Drawable popupBg) {
+        mSkbBg = skbBg;
+        mBalloonBg = balloonBg;
+        mPopupBg = popupBg;
+    }
+
+    public Drawable getSkbBackground() {
+        return mSkbBg;
+    }
+
+    public Drawable getBalloonBackground() {
+        return mBalloonBg;
+    }
+
+    public Drawable getPopupBackground() {
+        return mPopupBg;
+    }
+
+    public void setMargins(float xMargin, float yMargin) {
+        mXMargin = xMargin;
+        mYMargin = yMargin;
+    }
+
+    public float getXMargin() {
+        return mXMargin;
+    }
+
+    public float getYMargin() {
+        return mYMargin;
+    }
+
+    public SoftKeyType createKeyType(int id, Drawable bg, Drawable hlBg) {
+        return new SoftKeyType(id, bg, hlBg);
+    }
+
+    public boolean addKeyType(SoftKeyType keyType) {
+        // The newly added item should have the right id.
+        if (mKeyTypeList.size() != keyType.mKeyTypeId) return false;
+        mKeyTypeList.add(keyType);
+        return true;
+    }
+
+    public SoftKeyType getKeyType(int typeId) {
+        if (typeId < 0 || typeId > mKeyTypeList.size()) return null;
+        return mKeyTypeList.elementAt(typeId);
+    }
+
+    public void addDefaultKeyIcons(int keyCode, Drawable icon,
+            Drawable iconPopup) {
+        if (null == icon || null == iconPopup) return;
+
+        KeyIconRecord iconRecord = new KeyIconRecord();
+        iconRecord.icon = icon;
+        iconRecord.iconPopup = iconPopup;
+        iconRecord.keyCode = keyCode;
+
+        int size = mKeyIconRecords.size();
+        int pos = 0;
+        while (pos < size) {
+            if (mKeyIconRecords.get(pos).keyCode >= keyCode) break;
+            pos++;
+        }
+        mKeyIconRecords.add(pos, iconRecord);
+    }
+
+    public Drawable getDefaultKeyIcon(int keyCode) {
+        int size = mKeyIconRecords.size();
+        int pos = 0;
+        while (pos < size) {
+            KeyIconRecord iconRecord = mKeyIconRecords.get(pos);
+            if (iconRecord.keyCode < keyCode) {
+                pos++;
+                continue;
+            }
+            if (iconRecord.keyCode == keyCode) {
+                return iconRecord.icon;
+            }
+            return null;
+        }
+        return null;
+    }
+
+    public Drawable getDefaultKeyIconPopup(int keyCode) {
+        int size = mKeyIconRecords.size();
+        int pos = 0;
+        while (pos < size) {
+            KeyIconRecord iconRecord = mKeyIconRecords.get(pos);
+            if (iconRecord.keyCode < keyCode) {
+                pos++;
+                continue;
+            }
+            if (iconRecord.keyCode == keyCode) {
+                return iconRecord.iconPopup;
+            }
+            return null;
+        }
+        return null;
+    }
+
+    public void addDefaultKey(int keyId, SoftKey softKey) {
+        if (null == softKey) return;
+
+        KeyRecord keyRecord = new KeyRecord();
+        keyRecord.keyId = keyId;
+        keyRecord.softKey = softKey;
+
+        int size = mKeyRecords.size();
+        int pos = 0;
+        while (pos < size) {
+            if (mKeyRecords.get(pos).keyId >= keyId) break;
+            pos++;
+        }
+        mKeyRecords.add(pos, keyRecord);
+    }
+
+    public SoftKey getDefaultKey(int keyId) {
+        int size = mKeyRecords.size();
+        int pos = 0;
+        while (pos < size) {
+            KeyRecord keyRecord = mKeyRecords.get(pos);
+            if (keyRecord.keyId < keyId) {
+                pos++;
+                continue;
+            }
+            if (keyRecord.keyId == keyId) {
+                return keyRecord.softKey;
+            }
+            return null;
+        }
+        return null;
+    }
+}
+
+
+class SoftKeyType {
+    public static final int KEYTYPE_ID_NORMAL_KEY = 0;
+
+    public int mKeyTypeId;
+    public Drawable mKeyBg;
+    public Drawable mKeyHlBg;
+    public int mColor;
+    public int mColorHl;
+    public int mColorBalloon;
+
+    SoftKeyType(int id, Drawable bg, Drawable hlBg) {
+        mKeyTypeId = id;
+        mKeyBg = bg;
+        mKeyHlBg = hlBg;
+    }
+
+    public void setColors(int color, int colorHl, int colorBalloon) {
+        mColor = color;
+        mColorHl = colorHl;
+        mColorBalloon = colorBalloon;
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SoftKey.java b/PinyinIME/src/com/android/inputmethod/pinyin/SoftKey.java
new file mode 100644
index 0000000..67eaf29
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/SoftKey.java
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Class for soft keys which defined in the keyboard xml file. A soft key can be
+ * a basic key or a toggling key.
+ * 
+ * @see com.android.inputmethod.pinyin.SoftKeyToggle
+ */
+public class SoftKey {
+    protected static final int KEYMASK_REPEAT = 0x10000000;
+    protected static final int KEYMASK_BALLOON = 0x20000000;
+
+    /**
+     * For a finger touch device, after user presses a key, there will be some
+     * consequent moving events because of the changing in touching pressure. If
+     * the moving distance in x is within this threshold, the moving events will
+     * be ignored.
+     */
+    public static final int MAX_MOVE_TOLERANCE_X = 0;
+
+    /**
+     * For a finger touch device, after user presses a key, there will be some
+     * consequent moving events because of the changing in touching pressure. If
+     * the moving distance in y is within this threshold, the moving events will
+     * be ignored.
+     */
+    public static final int MAX_MOVE_TOLERANCE_Y = 0;
+
+    /**
+     * Used to indicate the type and attributes of this key. the lowest 8 bits
+     * should be reserved for SoftkeyToggle.
+     */
+    protected int mKeyMask;
+
+    protected SoftKeyType mKeyType;
+
+    protected Drawable mKeyIcon;
+
+    protected Drawable mKeyIconPopup;
+
+    protected String mKeyLabel;
+
+    protected int mKeyCode;
+
+    /**
+     * If this value is not 0, this key can be used to popup a sub soft keyboard
+     * when user presses it for some time.
+     */
+    public int mPopupSkbId;
+
+    public float mLeftF;
+    public float mRightF;
+    public float mTopF;
+    public float mBottomF;
+    public int mLeft;
+    public int mRight;
+    public int mTop;
+    public int mBottom;
+
+    public void setKeyType(SoftKeyType keyType, Drawable keyIcon,
+            Drawable keyIconPopup) {
+        mKeyType = keyType;
+        mKeyIcon = keyIcon;
+        mKeyIconPopup = keyIconPopup;
+    }
+
+    // The caller guarantees that all parameters are in [0, 1]
+    public void setKeyDimensions(float left, float top, float right,
+            float bottom) {
+        mLeftF = left;
+        mTopF = top;
+        mRightF = right;
+        mBottomF = bottom;
+    }
+
+    public void setKeyAttribute(int keyCode, String label, boolean repeat,
+            boolean balloon) {
+        mKeyCode = keyCode;
+        mKeyLabel = label;
+
+        if (repeat) {
+            mKeyMask |= KEYMASK_REPEAT;
+        } else {
+            mKeyMask &= (~KEYMASK_REPEAT);
+        }
+
+        if (balloon) {
+            mKeyMask |= KEYMASK_BALLOON;
+        } else {
+            mKeyMask &= (~KEYMASK_BALLOON);
+        }
+    }
+
+    public void setPopupSkbId(int popupSkbId) {
+        mPopupSkbId = popupSkbId;
+    }
+
+    // Call after setKeyDimensions(). The caller guarantees that the
+    // keyboard with and height are valid.
+    public void setSkbCoreSize(int skbWidth, int skbHeight) {
+        mLeft = (int) (mLeftF * skbWidth);
+        mRight = (int) (mRightF * skbWidth);
+        mTop = (int) (mTopF * skbHeight);
+        mBottom = (int) (mBottomF * skbHeight);
+    }
+
+    public Drawable getKeyIcon() {
+        return mKeyIcon;
+    }
+
+    public Drawable getKeyIconPopup() {
+        if (null != mKeyIconPopup) {
+            return mKeyIconPopup;
+        }
+        return mKeyIcon;
+    }
+
+    public int getKeyCode() {
+        return mKeyCode;
+    }
+
+    public String getKeyLabel() {
+        return mKeyLabel;
+    }
+
+    public void changeCase(boolean upperCase) {
+        if (null != mKeyLabel) {
+            if (upperCase)
+                mKeyLabel = mKeyLabel.toUpperCase();
+            else
+                mKeyLabel = mKeyLabel.toLowerCase();
+        }
+    }
+
+    public Drawable getKeyBg() {
+        return mKeyType.mKeyBg;
+    }
+
+    public Drawable getKeyHlBg() {
+        return mKeyType.mKeyHlBg;
+    }
+
+    public int getColor() {
+        return mKeyType.mColor;
+    }
+
+    public int getColorHl() {
+        return mKeyType.mColorHl;
+    }
+
+    public int getColorBalloon() {
+        return mKeyType.mColorBalloon;
+    }
+
+    public boolean isKeyCodeKey() {
+        if (mKeyCode > 0) return true;
+        return false;
+    }
+
+    public boolean isUserDefKey() {
+        if (mKeyCode < 0) return true;
+        return false;
+    }
+
+    public boolean isUniStrKey() {
+        if (null != mKeyLabel && mKeyCode == 0) return true;
+        return false;
+    }
+
+    public boolean needBalloon() {
+        return (mKeyMask & KEYMASK_BALLOON) != 0;
+    }
+
+    public boolean repeatable() {
+        return (mKeyMask & KEYMASK_REPEAT) != 0;
+    }
+
+    public int getPopupResId() {
+        return mPopupSkbId;
+    }
+
+    public int width() {
+        return mRight - mLeft;
+    }
+
+    public int height() {
+        return mBottom - mTop;
+    }
+
+    public boolean moveWithinKey(int x, int y) {
+        if (mLeft - MAX_MOVE_TOLERANCE_X <= x
+                && mTop - MAX_MOVE_TOLERANCE_Y <= y
+                && mRight + MAX_MOVE_TOLERANCE_X > x
+                && mBottom + MAX_MOVE_TOLERANCE_Y > y) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        String str = "\n";
+        str += "  keyCode: " + String.valueOf(mKeyCode) + "\n";
+        str += "  keyMask: " + String.valueOf(mKeyMask) + "\n";
+        str += "  keyLabel: " + (mKeyLabel == null ? "null" : mKeyLabel) + "\n";
+        str += "  popupResId: " + String.valueOf(mPopupSkbId) + "\n";
+        str += "  Position: " + String.valueOf(mLeftF) + ", "
+                + String.valueOf(mTopF) + ", " + String.valueOf(mRightF) + ", "
+                + String.valueOf(mBottomF) + "\n";
+        return str;
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyToggle.java b/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyToggle.java
new file mode 100644
index 0000000..89ff2fe
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyToggle.java
@@ -0,0 +1,283 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Class for soft keys which defined in the keyboard xml file. A soft key can be
+ * a basic key or a toggling key.
+ * 
+ * @see com.android.inputmethod.pinyin.SoftKey
+ */
+public class SoftKeyToggle extends SoftKey {
+    /**
+     * The current state number is stored in the lowest 8 bits of mKeyMask, this
+     * mask is used to get the state number. If the current state is 0, the
+     * normal state is enabled; if the current state is more than 0, a toggle
+     * state in the toggle state chain will be enabled.
+     */
+    private static final int KEYMASK_TOGGLE_STATE = 0x000000ff;
+
+    private ToggleState mToggleState;
+
+    public int getToggleStateId() {
+        return (mKeyMask & KEYMASK_TOGGLE_STATE);
+    }
+
+    // The state id should be valid, and less than 255.
+    // If resetIfNotFound is true and there is no such toggle state with the
+    // given id, the key state will be reset.
+    // If the key state is newly changed (enabled to the given state, or
+    // reseted) and needs re-draw, return true.
+    public boolean enableToggleState(int stateId, boolean resetIfNotFound) {
+        int oldStateId = (mKeyMask & KEYMASK_TOGGLE_STATE);
+        if (oldStateId == stateId) return false;
+
+        mKeyMask &= (~KEYMASK_TOGGLE_STATE);
+        if (stateId > 0) {
+            mKeyMask |= (KEYMASK_TOGGLE_STATE & stateId);
+            if (getToggleState() == null) {
+                mKeyMask &= (~KEYMASK_TOGGLE_STATE);
+                if (!resetIfNotFound && oldStateId > 0) {
+                    mKeyMask |= (KEYMASK_TOGGLE_STATE & oldStateId);
+                }
+                return resetIfNotFound;
+            } else {
+                return true;
+            }
+        } else {
+            return true;
+        }
+    }
+
+    // The state id should be valid, and less than 255.
+    // If resetIfNotFound is true and there is no such toggle state with the
+    // given id, the key state will be reset.
+    // If the key state is newly changed and needs re-draw, return true.
+    public boolean disableToggleState(int stateId, boolean resetIfNotFound) {
+        int oldStateId = (mKeyMask & KEYMASK_TOGGLE_STATE);
+        if (oldStateId == stateId) {
+            mKeyMask &= (~KEYMASK_TOGGLE_STATE);
+            return stateId != 0;
+        }
+
+        if (resetIfNotFound) {
+            mKeyMask &= (~KEYMASK_TOGGLE_STATE);
+            return oldStateId != 0;
+        }
+        return false;
+    }
+
+    // Clear any toggle state. If the key needs re-draw, return true.
+    public boolean disableAllToggleStates() {
+        int oldStateId = (mKeyMask & KEYMASK_TOGGLE_STATE);
+        mKeyMask &= (~KEYMASK_TOGGLE_STATE);
+        return oldStateId != 0;
+    }
+
+    @Override
+    public Drawable getKeyIcon() {
+        ToggleState state = getToggleState();
+        if (null != state) return state.mKeyIcon;
+        return super.getKeyIcon();
+    }
+
+    @Override
+    public Drawable getKeyIconPopup() {
+        ToggleState state = getToggleState();
+        if (null != state) {
+            if (null != state.mKeyIconPopup) {
+                return state.mKeyIconPopup;
+            } else {
+                return state.mKeyIcon;
+            }
+        }
+        return super.getKeyIconPopup();
+    }
+
+    @Override
+    public int getKeyCode() {
+        ToggleState state = getToggleState();
+        if (null != state) return state.mKeyCode;
+        return mKeyCode;
+    }
+
+    @Override
+    public String getKeyLabel() {
+        ToggleState state = getToggleState();
+        if (null != state) return state.mKeyLabel;
+        return mKeyLabel;
+    }
+
+    @Override
+    public Drawable getKeyBg() {
+        ToggleState state = getToggleState();
+        if (null != state && null != state.mKeyType) {
+            return state.mKeyType.mKeyBg;
+        }
+        return mKeyType.mKeyBg;
+    }
+
+    @Override
+    public Drawable getKeyHlBg() {
+        ToggleState state = getToggleState();
+        if (null != state && null != state.mKeyType) {
+            return state.mKeyType.mKeyHlBg;
+        }
+        return mKeyType.mKeyHlBg;
+    }
+
+    @Override
+    public int getColor() {
+        ToggleState state = getToggleState();
+        if (null != state && null != state.mKeyType) {
+            return state.mKeyType.mColor;
+        }
+        return mKeyType.mColor;
+    }
+
+    @Override
+    public int getColorHl() {
+        ToggleState state = getToggleState();
+        if (null != state && null != state.mKeyType) {
+            return state.mKeyType.mColorHl;
+        }
+        return mKeyType.mColorHl;
+    }
+
+    @Override
+    public int getColorBalloon() {
+        ToggleState state = getToggleState();
+        if (null != state && null != state.mKeyType) {
+            return state.mKeyType.mColorBalloon;
+        }
+        return mKeyType.mColorBalloon;
+    }
+
+    @Override
+    public boolean isKeyCodeKey() {
+        ToggleState state = getToggleState();
+        if (null != state) {
+            if (state.mKeyCode > 0) return true;
+            return false;
+        }
+        return super.isKeyCodeKey();
+    }
+
+    @Override
+    public boolean isUserDefKey() {
+        ToggleState state = getToggleState();
+        if (null != state) {
+            if (state.mKeyCode < 0) return true;
+            return false;
+        }
+        return super.isUserDefKey();
+    }
+
+    @Override
+    public boolean isUniStrKey() {
+        ToggleState state = getToggleState();
+        if (null != state) {
+            if (null != state.mKeyLabel && state.mKeyCode == 0) {
+                return true;
+            }
+            return false;
+        }
+        return super.isUniStrKey();
+    }
+
+    @Override
+    public boolean needBalloon() {
+        ToggleState state = getToggleState();
+        if (null != state) {
+            return (state.mIdAndFlags & KEYMASK_BALLOON) != 0;
+        }
+        return super.needBalloon();
+    }
+
+    @Override
+    public boolean repeatable() {
+        ToggleState state = getToggleState();
+        if (null != state) {
+            return (state.mIdAndFlags & KEYMASK_REPEAT) != 0;
+        }
+        return super.repeatable();
+    }
+
+    @Override
+    public void changeCase(boolean lowerCase) {
+        ToggleState state = getToggleState();
+        if (null != state && null != state.mKeyLabel) {
+            if (lowerCase)
+                state.mKeyLabel = state.mKeyLabel.toLowerCase();
+            else
+                state.mKeyLabel = state.mKeyLabel.toUpperCase();
+        }
+    }
+
+    public ToggleState createToggleState() {
+        return new ToggleState();
+    }
+
+    public boolean setToggleStates(ToggleState rootState) {
+        if (null == rootState) return false;
+        mToggleState = rootState;
+        return true;
+    }
+
+    private ToggleState getToggleState() {
+        int stateId = (mKeyMask & KEYMASK_TOGGLE_STATE);
+        if (0 == stateId) return null;
+
+        ToggleState state = mToggleState;
+        while ((null != state)
+                && (state.mIdAndFlags & KEYMASK_TOGGLE_STATE) != stateId) {
+            state = state.mNextState;
+        }
+        return state;
+    }
+
+    public class ToggleState {
+        // The id should be bigger than 0;
+        private int mIdAndFlags;
+        public SoftKeyType mKeyType;
+        public int mKeyCode;
+        public Drawable mKeyIcon;
+        public Drawable mKeyIconPopup;
+        public String mKeyLabel;
+        public ToggleState mNextState;
+
+        public void setStateId(int stateId) {
+            mIdAndFlags |= (stateId & KEYMASK_TOGGLE_STATE);
+        }
+
+        public void setStateFlags(boolean repeat, boolean balloon) {
+            if (repeat) {
+                mIdAndFlags |= KEYMASK_REPEAT;
+            } else {
+                mIdAndFlags &= (~KEYMASK_REPEAT);
+            }
+
+            if (balloon) {
+                mIdAndFlags |= KEYMASK_BALLOON;
+            } else {
+                mIdAndFlags &= (~KEYMASK_BALLOON);
+            }
+        }
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyboard.java b/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyboard.java
new file mode 100644
index 0000000..b8cc504
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyboard.java
@@ -0,0 +1,520 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import com.android.inputmethod.pinyin.InputModeSwitcher.ToggleStates;
+
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class used to represent a soft keyboard definition, including the height, the
+ * background image, the image for high light, the keys, etc.
+ */
+public class SoftKeyboard {
+    /** The XML resource id for this soft keyboard. */
+    private int mSkbXmlId;
+
+    /** Do we need to cache this soft keyboard? */
+    private boolean mCacheFlag;
+
+    /**
+     * After user switches to this soft keyboard, if this flag is true, this
+     * soft keyboard will be kept unless explicit switching operation is
+     * performed, otherwise IME will switch back to the previous keyboard layout
+     * whenever user clicks on any none-function key.
+     **/
+    private boolean mStickyFlag;
+
+    /**
+     * The cache id for this soft keyboard. It is used to identify it in the
+     * soft keyboard pool.
+     */
+    private int mCacheId;
+
+    /**
+     * Used to indicate whether this soft keyboard is newly loaded from an XML
+     * file or is just gotten from the soft keyboard pool.
+     */
+    private boolean mNewlyLoadedFlag = true;
+
+    /** The width of the soft keyboard. */
+    private int mSkbCoreWidth;
+
+    /** The height of the soft keyboard. */
+    private int mSkbCoreHeight;
+
+    /** The soft keyboard template for this soft keyboard. */
+    private SkbTemplate mSkbTemplate;
+
+    /** Used to indicate whether this soft keyboard is a QWERTY keyboard. */
+    private boolean mIsQwerty;
+
+    /**
+     * When {@link #mIsQwerty} is true, this member is Used to indicate that the
+     * soft keyboard should be displayed in uppercase.
+     */
+    private boolean mIsQwertyUpperCase;
+
+    /**
+     * The id of the rows which are enabled. Rows with id
+     * {@link KeyRow#ALWAYS_SHOW_ROW_ID} are always enabled.
+     */
+    private int mEnabledRowId;
+
+    /**
+     * Rows in this soft keyboard. Each row has a id. Only matched rows will be
+     * enabled.
+     */
+    private List<KeyRow> mKeyRows;
+
+    /**
+     * Background of the soft keyboard. If it is null, the one in the soft
+     * keyboard template will be used.
+     **/
+    public Drawable mSkbBg;
+
+    /**
+     * Background for key balloon. If it is null, the one in the soft keyboard
+     * template will be used.
+     **/
+    private Drawable mBalloonBg;
+
+    /**
+     * Background for popup mini soft keyboard. If it is null, the one in the
+     * soft keyboard template will be used.
+     **/
+    private Drawable mPopupBg;
+
+    /** The left and right margin of a key. */
+    private float mKeyXMargin = 0;
+
+    /** The top and bottom margin of a key. */
+    private float mKeyYMargin = 0;
+
+    private Rect mTmpRect = new Rect();
+
+    public SoftKeyboard(int skbXmlId, SkbTemplate skbTemplate, int skbWidth,
+            int skbHeight) {
+        mSkbXmlId = skbXmlId;
+        mSkbTemplate = skbTemplate;
+        mSkbCoreWidth = skbWidth;
+        mSkbCoreHeight = skbHeight;
+    }
+
+    public void setFlags(boolean cacheFlag, boolean stickyFlag,
+            boolean isQwerty, boolean isQwertyUpperCase) {
+        mCacheFlag = cacheFlag;
+        mStickyFlag = stickyFlag;
+        mIsQwerty = isQwerty;
+        mIsQwertyUpperCase = isQwertyUpperCase;
+    }
+
+    public boolean getCacheFlag() {
+        return mCacheFlag;
+    }
+
+    public void setCacheId(int cacheId) {
+        mCacheId = cacheId;
+    }
+
+    public boolean getStickyFlag() {
+        return mStickyFlag;
+    }
+
+    public void setSkbBackground(Drawable skbBg) {
+        mSkbBg = skbBg;
+    }
+
+    public void setPopupBackground(Drawable popupBg) {
+        mPopupBg = popupBg;
+    }
+
+    public void setKeyBalloonBackground(Drawable balloonBg) {
+        mBalloonBg = balloonBg;
+    }
+
+    public void setKeyMargins(float xMargin, float yMargin) {
+        mKeyXMargin = xMargin;
+        mKeyYMargin = yMargin;
+    }
+
+    public int getCacheId() {
+        return mCacheId;
+    }
+
+    public void reset() {
+        if (null != mKeyRows) mKeyRows.clear();
+    }
+
+    public void setNewlyLoadedFlag(boolean newlyLoadedFlag) {
+        mNewlyLoadedFlag = newlyLoadedFlag;
+    }
+
+    public boolean getNewlyLoadedFlag() {
+        return mNewlyLoadedFlag;
+    }
+
+    public void beginNewRow(int rowId, float yStartingPos) {
+        if (null == mKeyRows) mKeyRows = new ArrayList<KeyRow>();
+        KeyRow keyRow = new KeyRow();
+        keyRow.mRowId = rowId;
+        keyRow.mTopF = yStartingPos;
+        keyRow.mBottomF = yStartingPos;
+        keyRow.mSoftKeys = new ArrayList<SoftKey>();
+        mKeyRows.add(keyRow);
+    }
+
+    public boolean addSoftKey(SoftKey softKey) {
+        if (mKeyRows.size() == 0) return false;
+        KeyRow keyRow = mKeyRows.get(mKeyRows.size() - 1);
+        if (null == keyRow) return false;
+        List<SoftKey> softKeys = keyRow.mSoftKeys;
+
+        softKey.setSkbCoreSize(mSkbCoreWidth, mSkbCoreHeight);
+        softKeys.add(softKey);
+        if (softKey.mTopF < keyRow.mTopF) {
+            keyRow.mTopF = softKey.mTopF;
+        }
+        if (softKey.mBottomF > keyRow.mBottomF) {
+            keyRow.mBottomF = softKey.mBottomF;
+        }
+        return true;
+    }
+
+    public int getSkbXmlId() {
+        return mSkbXmlId;
+    }
+
+    // Set the size of the soft keyboard core. In other words, the background's
+    // padding are not counted.
+    public void setSkbCoreSize(int skbCoreWidth, int skbCoreHeight) {
+        if (null == mKeyRows
+                || (skbCoreWidth == mSkbCoreWidth && skbCoreHeight == mSkbCoreHeight)) {
+            return;
+        }
+        for (int row = 0; row < mKeyRows.size(); row++) {
+            KeyRow keyRow = mKeyRows.get(row);
+            keyRow.mBottom = (int) (skbCoreHeight * keyRow.mBottomF);
+            keyRow.mTop = (int) (skbCoreHeight * keyRow.mTopF);
+
+            List<SoftKey> softKeys = keyRow.mSoftKeys;
+            for (int i = 0; i < softKeys.size(); i++) {
+                SoftKey softKey = softKeys.get(i);
+                softKey.setSkbCoreSize(skbCoreWidth, skbCoreHeight);
+            }
+        }
+        mSkbCoreWidth = skbCoreWidth;
+        mSkbCoreHeight = skbCoreHeight;
+    }
+
+    public int getSkbCoreWidth() {
+        return mSkbCoreWidth;
+    }
+
+    public int getSkbCoreHeight() {
+        return mSkbCoreHeight;
+    }
+
+    public int getSkbTotalWidth() {
+        Rect padding = getPadding();
+        return mSkbCoreWidth + padding.left + padding.right;
+    }
+
+    public int getSkbTotalHeight() {
+        Rect padding = getPadding();
+        return mSkbCoreHeight + padding.top + padding.bottom;
+    }
+
+    public int getKeyXMargin() {
+        Environment env = Environment.getInstance();
+        return (int) (mKeyXMargin * mSkbCoreWidth * env.getKeyXMarginFactor());
+    }
+
+    public int getKeyYMargin() {
+        Environment env = Environment.getInstance();
+        return (int) (mKeyYMargin * mSkbCoreHeight * env.getKeyYMarginFactor());
+    }
+
+    public Drawable getSkbBackground() {
+        if (null != mSkbBg) return mSkbBg;
+        return mSkbTemplate.getSkbBackground();
+    }
+
+    public Drawable getBalloonBackground() {
+        if (null != mBalloonBg) return mBalloonBg;
+        return mSkbTemplate.getBalloonBackground();
+    }
+
+    public Drawable getPopupBackground() {
+        if (null != mPopupBg) return mPopupBg;
+        return mSkbTemplate.getPopupBackground();
+    }
+
+    public int getRowNum() {
+        if (null != mKeyRows) {
+            return mKeyRows.size();
+        }
+        return 0;
+    }
+
+    public KeyRow getKeyRowForDisplay(int row) {
+        if (null != mKeyRows && mKeyRows.size() > row) {
+            KeyRow keyRow = mKeyRows.get(row);
+            if (KeyRow.ALWAYS_SHOW_ROW_ID == keyRow.mRowId
+                    || keyRow.mRowId == mEnabledRowId) {
+                return keyRow;
+            }
+        }
+        return null;
+    }
+
+    public SoftKey getKey(int row, int location) {
+        if (null != mKeyRows && mKeyRows.size() > row) {
+            List<SoftKey> softKeys = mKeyRows.get(row).mSoftKeys;
+            if (softKeys.size() > location) {
+                return softKeys.get(location);
+            }
+        }
+        return null;
+    }
+
+    public SoftKey mapToKey(int x, int y) {
+        if (null == mKeyRows) {
+            return null;
+        }
+        // If the position is inside the rectangle of a certain key, return that
+        // key.
+        int rowNum = mKeyRows.size();
+        for (int row = 0; row < rowNum; row++) {
+            KeyRow keyRow = mKeyRows.get(row);
+            if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId
+                    && keyRow.mRowId != mEnabledRowId) continue;
+            if (keyRow.mTop > y && keyRow.mBottom <= y) continue;
+
+            List<SoftKey> softKeys = keyRow.mSoftKeys;
+            int keyNum = softKeys.size();
+            for (int i = 0; i < keyNum; i++) {
+                SoftKey sKey = softKeys.get(i);
+                if (sKey.mLeft <= x && sKey.mTop <= y && sKey.mRight > x
+                        && sKey.mBottom > y) {
+                    return sKey;
+                }
+            }
+        }
+
+        // If the position is outside the rectangles of all keys, find the
+        // nearest one.
+        SoftKey nearestKey = null;
+        float nearestDis = Float.MAX_VALUE;
+        for (int row = 0; row < rowNum; row++) {
+            KeyRow keyRow = mKeyRows.get(row);
+            if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId
+                    && keyRow.mRowId != mEnabledRowId) continue;
+            if (keyRow.mTop > y && keyRow.mBottom <= y) continue;
+
+            List<SoftKey> softKeys = keyRow.mSoftKeys;
+            int keyNum = softKeys.size();
+            for (int i = 0; i < keyNum; i++) {
+                SoftKey sKey = softKeys.get(i);
+                int disx = (sKey.mLeft + sKey.mRight) / 2 - x;
+                int disy = (sKey.mTop + sKey.mBottom) / 2 - y;
+                float dis = disx * disx + disy * disy;
+                if (dis < nearestDis) {
+                    nearestDis = dis;
+                    nearestKey = sKey;
+                }
+            }
+        }
+        return nearestKey;
+    }
+
+    public void switchQwertyMode(int toggle_state_id, boolean upperCase) {
+        if (!mIsQwerty) return;
+
+        int rowNum = mKeyRows.size();
+        for (int row = 0; row < rowNum; row++) {
+            KeyRow keyRow = mKeyRows.get(row);
+            List<SoftKey> softKeys = keyRow.mSoftKeys;
+            int keyNum = softKeys.size();
+            for (int i = 0; i < keyNum; i++) {
+                SoftKey sKey = softKeys.get(i);
+                if (sKey instanceof SoftKeyToggle) {
+                    ((SoftKeyToggle) sKey).enableToggleState(toggle_state_id,
+                            true);
+                }
+                if (sKey.mKeyCode >= KeyEvent.KEYCODE_A
+                        && sKey.mKeyCode <= KeyEvent.KEYCODE_Z) {
+                    sKey.changeCase(upperCase);
+                }
+            }
+        }
+    }
+
+    public void enableToggleState(int toggleStateId, boolean resetIfNotFound) {
+        int rowNum = mKeyRows.size();
+        for (int row = 0; row < rowNum; row++) {
+            KeyRow keyRow = mKeyRows.get(row);
+            List<SoftKey> softKeys = keyRow.mSoftKeys;
+            int keyNum = softKeys.size();
+            for (int i = 0; i < keyNum; i++) {
+                SoftKey sKey = softKeys.get(i);
+                if (sKey instanceof SoftKeyToggle) {
+                    ((SoftKeyToggle) sKey).enableToggleState(toggleStateId,
+                            resetIfNotFound);
+                }
+            }
+        }
+    }
+
+    public void disableToggleState(int toggleStateId, boolean resetIfNotFound) {
+        int rowNum = mKeyRows.size();
+        for (int row = 0; row < rowNum; row++) {
+            KeyRow keyRow = mKeyRows.get(row);
+            List<SoftKey> softKeys = keyRow.mSoftKeys;
+            int keyNum = softKeys.size();
+            for (int i = 0; i < keyNum; i++) {
+                SoftKey sKey = softKeys.get(i);
+                if (sKey instanceof SoftKeyToggle) {
+                    ((SoftKeyToggle) sKey).disableToggleState(toggleStateId,
+                            resetIfNotFound);
+                }
+            }
+        }
+    }
+
+    public void enableToggleStates(ToggleStates toggleStates) {
+        if (null == toggleStates) return;
+
+        enableRow(toggleStates.mRowIdToEnable);
+
+        boolean isQwerty = toggleStates.mQwerty;
+        boolean isQwertyUpperCase = toggleStates.mQwertyUpperCase;
+        boolean needUpdateQwerty = (isQwerty && mIsQwerty && (mIsQwertyUpperCase != isQwertyUpperCase));
+        int states[] = toggleStates.mKeyStates;
+        int statesNum = toggleStates.mKeyStatesNum;
+
+        int rowNum = mKeyRows.size();
+        for (int row = 0; row < rowNum; row++) {
+            KeyRow keyRow = mKeyRows.get(row);
+            if (KeyRow.ALWAYS_SHOW_ROW_ID != keyRow.mRowId
+                    && keyRow.mRowId != mEnabledRowId) {
+                continue;
+            }
+            List<SoftKey> softKeys = keyRow.mSoftKeys;
+            int keyNum = softKeys.size();
+            for (int keyPos = 0; keyPos < keyNum; keyPos++) {
+                SoftKey sKey = softKeys.get(keyPos);
+                if (sKey instanceof SoftKeyToggle) {
+                    for (int statePos = 0; statePos < statesNum; statePos++) {
+                        ((SoftKeyToggle) sKey).enableToggleState(
+                                states[statePos], statePos == 0);
+                    }
+                    if (0 == statesNum) {
+                        ((SoftKeyToggle) sKey).disableAllToggleStates();
+                    }
+                }
+                if (needUpdateQwerty) {
+                    if (sKey.mKeyCode >= KeyEvent.KEYCODE_A
+                            && sKey.mKeyCode <= KeyEvent.KEYCODE_Z) {
+                        sKey.changeCase(isQwertyUpperCase);
+                    }
+                }
+            }
+        }
+        mIsQwertyUpperCase = isQwertyUpperCase;
+    }
+
+    private Rect getPadding() {
+        mTmpRect.set(0, 0, 0, 0);
+        Drawable skbBg = getSkbBackground();
+        if (null == skbBg) return mTmpRect;
+        skbBg.getPadding(mTmpRect);
+        return mTmpRect;
+    }
+
+    /**
+     * Enable a row with the give toggle Id. Rows with other toggle ids (except
+     * the id {@link KeyRow#ALWAYS_SHOW_ROW_ID}) will be disabled.
+     * 
+     * @param rowId The row id to enable.
+     * @return True if the soft keyboard requires redrawing.
+     */
+    private boolean enableRow(int rowId) {
+        if (KeyRow.ALWAYS_SHOW_ROW_ID == rowId) return false;
+
+        boolean enabled = false;
+        int rowNum = mKeyRows.size();
+        for (int row = rowNum - 1; row >= 0; row--) {
+            if (mKeyRows.get(row).mRowId == rowId) {
+                enabled = true;
+                break;
+            }
+        }
+        if (enabled) {
+            mEnabledRowId = rowId;
+        }
+        return enabled;
+    }
+
+    @Override
+    public String toString() {
+        String str = "------------------SkbInfo----------------------\n";
+        String endStr = "-----------------------------------------------\n";
+        str += "Width: " + String.valueOf(mSkbCoreWidth) + "\n";
+        str += "Height: " + String.valueOf(mSkbCoreHeight) + "\n";
+        str += "KeyRowNum: " + mKeyRows == null ? "0" : String.valueOf(mKeyRows
+                .size())
+                + "\n";
+        if (null == mKeyRows) return str + endStr;
+        int rowNum = mKeyRows.size();
+        for (int row = 0; row < rowNum; row++) {
+            KeyRow keyRow = mKeyRows.get(row);
+            List<SoftKey> softKeys = keyRow.mSoftKeys;
+            int keyNum = softKeys.size();
+            for (int i = 0; i < softKeys.size(); i++) {
+                str += "-key " + String.valueOf(i) + ":"
+                        + softKeys.get(i).toString();
+            }
+        }
+        return str + endStr;
+    }
+
+    public String toShortString() {
+        return super.toString();
+    }
+
+    class KeyRow {
+        static final int ALWAYS_SHOW_ROW_ID = -1;
+        static final int DEFAULT_ROW_ID = 0;
+
+        List<SoftKey> mSoftKeys;
+        /**
+         * If the row id is {@link #ALWAYS_SHOW_ROW_ID}, this row will always be
+         * enabled.
+         */
+        int mRowId;
+        float mTopF;
+        float mBottomF;
+        int mTop;
+        int mBottom;
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyboardView.java b/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyboardView.java
new file mode 100644
index 0000000..5543f33
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/SoftKeyboardView.java
@@ -0,0 +1,478 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import com.android.inputmethod.pinyin.SoftKeyboard.KeyRow;
+
+import java.util.List;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.drawable.Drawable;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Class used to show a soft keyboard.
+ * 
+ * A soft keyboard view should not handle touch event itself, because we do bias
+ * correction, need a global strategy to map an event into a proper view to
+ * achieve better user experience.
+ */
+public class SoftKeyboardView extends View {
+    /**
+     * The definition of the soft keyboard for the current this soft keyboard
+     * view.
+     */
+    private SoftKeyboard mSoftKeyboard;
+
+    /**
+     * The popup balloon hint for key press/release.
+     */
+    private BalloonHint mBalloonPopup;
+
+    /**
+     * The on-key balloon hint for key press/release. If it is null, on-key
+     * highlight will be drawn on th soft keyboard view directly.
+     */
+    private BalloonHint mBalloonOnKey;
+
+    /** Used to play key sounds. */
+    private SoundManager mSoundManager;
+
+    /** The last key pressed. */
+    private SoftKey mSoftKeyDown;
+
+    /** Used to indicate whether the user is holding on a key. */
+    private boolean mKeyPressed = false;
+
+    /**
+     * The location offset of the view to the keyboard container.
+     */
+    private int mOffsetToSkbContainer[] = new int[2];
+
+    /**
+     * The location of the desired hint view to the keyboard container.
+     */
+    private int mHintLocationToSkbContainer[] = new int[2];
+
+    /**
+     * Text size for normal key.
+     */
+    private int mNormalKeyTextSize;
+
+    /**
+     * Text size for function key.
+     */
+    private int mFunctionKeyTextSize;
+
+    /**
+     * Long press timer used to response long-press.
+     */
+    private SkbContainer.LongPressTimer mLongPressTimer;
+
+    /**
+     * Repeated events for long press
+     */
+    private boolean mRepeatForLongPress = false;
+
+    /**
+     * If this parameter is true, the balloon will never be dismissed even if
+     * user moves a lot from the pressed point.
+     */
+    private boolean mMovingNeverHidePopupBalloon = false;
+
+    /** Vibration for key press. */
+    private Vibrator mVibrator;
+
+    /** Vibration pattern for key press. */
+    protected long[] mVibratePattern = new long[] {1, 20};
+
+    /**
+     * The dirty rectangle used to mark the area to re-draw during key press and
+     * release. Currently, whenever we can invalidate(Rect), view will call
+     * onDraw() and we MUST draw the whole view. This dirty information is for
+     * future use.
+     */
+    private Rect mDirtyRect = new Rect();
+
+    private Paint mPaint;
+    private FontMetricsInt mFmi;
+    private boolean mDimSkb;
+
+    public SoftKeyboardView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mSoundManager = SoundManager.getInstance(mContext);
+
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mFmi = mPaint.getFontMetricsInt();
+    }
+
+    public boolean setSoftKeyboard(SoftKeyboard softSkb) {
+        if (null == softSkb) {
+            return false;
+        }
+        mSoftKeyboard = softSkb;
+        Drawable bg = softSkb.getSkbBackground();
+        if (null != bg) setBackgroundDrawable(bg);
+        return true;
+    }
+
+    public SoftKeyboard getSoftKeyboard() {
+        return mSoftKeyboard;
+    }
+
+    public void resizeKeyboard(int skbWidth, int skbHeight) {
+        mSoftKeyboard.setSkbCoreSize(skbWidth, skbHeight);
+    }
+
+    public void setBalloonHint(BalloonHint balloonOnKey,
+            BalloonHint balloonPopup, boolean movingNeverHidePopup) {
+        mBalloonOnKey = balloonOnKey;
+        mBalloonPopup = balloonPopup;
+        mMovingNeverHidePopupBalloon = movingNeverHidePopup;
+    }
+
+    public void setOffsetToSkbContainer(int offsetToSkbContainer[]) {
+        mOffsetToSkbContainer[0] = offsetToSkbContainer[0];
+        mOffsetToSkbContainer[1] = offsetToSkbContainer[1];
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int measuredWidth = 0;
+        int measuredHeight = 0;
+        if (null != mSoftKeyboard) {
+            measuredWidth = mSoftKeyboard.getSkbCoreWidth();
+            measuredHeight = mSoftKeyboard.getSkbCoreHeight();
+            measuredWidth += mPaddingLeft + mPaddingRight;
+            measuredHeight += mPaddingTop + mPaddingBottom;
+        }
+        setMeasuredDimension(measuredWidth, measuredHeight);
+    }
+
+    private void showBalloon(BalloonHint balloon, int balloonLocationToSkb[],
+            boolean movePress) {
+        long delay = BalloonHint.TIME_DELAY_SHOW;
+        if (movePress) delay = 0;
+        if (balloon.needForceDismiss()) {
+            balloon.delayedDismiss(0);
+        }
+        if (!balloon.isShowing()) {
+            balloon.delayedShow(delay, balloonLocationToSkb);
+        } else {
+            balloon.delayedUpdate(delay, balloonLocationToSkb, balloon
+                    .getWidth(), balloon.getHeight());
+        }
+        long b = System.currentTimeMillis();
+    }
+
+    public void resetKeyPress(long balloonDelay) {
+        if (!mKeyPressed) return;
+        mKeyPressed = false;
+        if (null != mBalloonOnKey) {
+            mBalloonOnKey.delayedDismiss(balloonDelay);
+        } else {
+            if (null != mSoftKeyDown) {
+                if (mDirtyRect.isEmpty()) {
+                    mDirtyRect.set(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
+                            mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
+                }
+                invalidate(mDirtyRect);
+            } else {
+                invalidate();
+            }
+        }
+        mBalloonPopup.delayedDismiss(balloonDelay);
+    }
+
+    // If movePress is true, means that this function is called because user
+    // moves his finger to this button. If movePress is false, means that this
+    // function is called when user just presses this key.
+    public SoftKey onKeyPress(int x, int y,
+            SkbContainer.LongPressTimer longPressTimer, boolean movePress) {
+        mKeyPressed = false;
+        boolean moveWithinPreviousKey = false;
+        if (movePress) {
+            SoftKey newKey = mSoftKeyboard.mapToKey(x, y);
+            if (newKey == mSoftKeyDown) moveWithinPreviousKey = true;
+            mSoftKeyDown = newKey;
+        } else {
+            mSoftKeyDown = mSoftKeyboard.mapToKey(x, y);
+        }
+        if (moveWithinPreviousKey || null == mSoftKeyDown) return mSoftKeyDown;
+        mKeyPressed = true;
+
+        if (!movePress) {
+            tryPlayKeyDown();
+            tryVibrate();
+        }
+
+        mLongPressTimer = longPressTimer;
+
+        if (!movePress) {
+            if (mSoftKeyDown.getPopupResId() > 0 || mSoftKeyDown.repeatable()) {
+                mLongPressTimer.startTimer();
+            }
+        } else {
+            mLongPressTimer.removeTimer();
+        }
+
+        int desired_width;
+        int desired_height;
+        float textSize;
+        Environment env = Environment.getInstance();
+
+        if (null != mBalloonOnKey) {
+            Drawable keyHlBg = mSoftKeyDown.getKeyHlBg();
+            mBalloonOnKey.setBalloonBackground(keyHlBg);
+
+            // Prepare the on-key balloon
+            int keyXMargin = mSoftKeyboard.getKeyXMargin();
+            int keyYMargin = mSoftKeyboard.getKeyYMargin();
+            desired_width = mSoftKeyDown.width() - 2 * keyXMargin;
+            desired_height = mSoftKeyDown.height() - 2 * keyYMargin;
+            textSize = env
+                    .getKeyTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId);
+            Drawable icon = mSoftKeyDown.getKeyIcon();
+            if (null != icon) {
+                mBalloonOnKey.setBalloonConfig(icon, desired_width,
+                        desired_height);
+            } else {
+                mBalloonOnKey.setBalloonConfig(mSoftKeyDown.getKeyLabel(),
+                        textSize, true, mSoftKeyDown.getColorHl(),
+                        desired_width, desired_height);
+            }
+
+            mHintLocationToSkbContainer[0] = mPaddingLeft + mSoftKeyDown.mLeft
+                    - (mBalloonOnKey.getWidth() - mSoftKeyDown.width()) / 2;
+            mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0];
+            mHintLocationToSkbContainer[1] = mPaddingTop
+                    + (mSoftKeyDown.mBottom - keyYMargin)
+                    - mBalloonOnKey.getHeight();
+            mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1];
+            showBalloon(mBalloonOnKey, mHintLocationToSkbContainer, movePress);
+        } else {
+            mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
+                    mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
+            invalidate(mDirtyRect);
+        }
+
+        // Prepare the popup balloon
+        if (mSoftKeyDown.needBalloon()) {
+            Drawable balloonBg = mSoftKeyboard.getBalloonBackground();
+            mBalloonPopup.setBalloonBackground(balloonBg);
+
+            desired_width = mSoftKeyDown.width() + env.getKeyBalloonWidthPlus();
+            desired_height = mSoftKeyDown.height()
+                    + env.getKeyBalloonHeightPlus();
+            textSize = env
+                    .getBalloonTextSize(SoftKeyType.KEYTYPE_ID_NORMAL_KEY != mSoftKeyDown.mKeyType.mKeyTypeId);
+            Drawable iconPopup = mSoftKeyDown.getKeyIconPopup();
+            if (null != iconPopup) {
+                mBalloonPopup.setBalloonConfig(iconPopup, desired_width,
+                        desired_height);
+            } else {
+                mBalloonPopup.setBalloonConfig(mSoftKeyDown.getKeyLabel(),
+                        textSize, mSoftKeyDown.needBalloon(), mSoftKeyDown
+                                .getColorBalloon(), desired_width,
+                        desired_height);
+            }
+
+            // The position to show.
+            mHintLocationToSkbContainer[0] = mPaddingLeft + mSoftKeyDown.mLeft
+                    + -(mBalloonPopup.getWidth() - mSoftKeyDown.width()) / 2;
+            mHintLocationToSkbContainer[0] += mOffsetToSkbContainer[0];
+            mHintLocationToSkbContainer[1] = mPaddingTop + mSoftKeyDown.mTop
+                    - mBalloonPopup.getHeight();
+            mHintLocationToSkbContainer[1] += mOffsetToSkbContainer[1];
+            showBalloon(mBalloonPopup, mHintLocationToSkbContainer, movePress);
+        } else {
+            mBalloonPopup.delayedDismiss(0);
+        }
+
+        if (mRepeatForLongPress) longPressTimer.startTimer();
+        return mSoftKeyDown;
+    }
+
+    public SoftKey onKeyRelease(int x, int y) {
+        mKeyPressed = false;
+        if (null == mSoftKeyDown) return null;
+
+        mLongPressTimer.removeTimer();
+
+        if (null != mBalloonOnKey) {
+            mBalloonOnKey.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
+        } else {
+            mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
+                    mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
+            invalidate(mDirtyRect);
+        }
+
+        if (mSoftKeyDown.needBalloon()) {
+            mBalloonPopup.delayedDismiss(BalloonHint.TIME_DELAY_DISMISS);
+        }
+
+        if (mSoftKeyDown.moveWithinKey(x - mPaddingLeft, y - mPaddingTop)) {
+            return mSoftKeyDown;
+        }
+        return null;
+    }
+
+    public SoftKey onKeyMove(int x, int y) {
+        if (null == mSoftKeyDown) return null;
+
+        if (mSoftKeyDown.moveWithinKey(x - mPaddingLeft, y - mPaddingTop)) {
+            return mSoftKeyDown;
+        }
+
+        // The current key needs to be updated.
+        mDirtyRect.union(mSoftKeyDown.mLeft, mSoftKeyDown.mTop,
+                mSoftKeyDown.mRight, mSoftKeyDown.mBottom);
+
+        if (mRepeatForLongPress) {
+            if (mMovingNeverHidePopupBalloon) {
+                return onKeyPress(x, y, mLongPressTimer, true);
+            }
+
+            if (null != mBalloonOnKey) {
+                mBalloonOnKey.delayedDismiss(0);
+            } else {
+                invalidate(mDirtyRect);
+            }
+
+            if (mSoftKeyDown.needBalloon()) {
+                mBalloonPopup.delayedDismiss(0);
+            }
+
+            if (null != mLongPressTimer) {
+                mLongPressTimer.removeTimer();
+            }
+            return onKeyPress(x, y, mLongPressTimer, true);
+        } else {
+            // When user moves between keys, repeated response is disabled.
+            return onKeyPress(x, y, mLongPressTimer, true);
+        }
+    }
+
+    private void tryVibrate() {
+        if (!Settings.getVibrate()) {
+            return;
+        }
+        if (mVibrator == null) {
+            mVibrator = new Vibrator();
+        }
+        mVibrator.vibrate(mVibratePattern, -1);
+    }
+
+    private void tryPlayKeyDown() {
+        if (Settings.getKeySound()) {
+            mSoundManager.playKeyDown();
+        }
+    }
+
+    public void dimSoftKeyboard(boolean dimSkb) {
+        mDimSkb = dimSkb;
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (null == mSoftKeyboard) return;
+
+        canvas.translate(mPaddingLeft, mPaddingTop);
+
+        Environment env = Environment.getInstance();
+        mNormalKeyTextSize = env.getKeyTextSize(false);
+        mFunctionKeyTextSize = env.getKeyTextSize(true);
+        // Draw the last soft keyboard
+        int rowNum = mSoftKeyboard.getRowNum();
+        int keyXMargin = mSoftKeyboard.getKeyXMargin();
+        int keyYMargin = mSoftKeyboard.getKeyYMargin();
+        for (int row = 0; row < rowNum; row++) {
+            KeyRow keyRow = mSoftKeyboard.getKeyRowForDisplay(row);
+            if (null == keyRow) continue;
+            List<SoftKey> softKeys = keyRow.mSoftKeys;
+            int keyNum = softKeys.size();
+            for (int i = 0; i < keyNum; i++) {
+                SoftKey softKey = softKeys.get(i);
+                if (SoftKeyType.KEYTYPE_ID_NORMAL_KEY == softKey.mKeyType.mKeyTypeId) {
+                    mPaint.setTextSize(mNormalKeyTextSize);
+                } else {
+                    mPaint.setTextSize(mFunctionKeyTextSize);
+                }
+                drawSoftKey(canvas, softKey, keyXMargin, keyYMargin);
+            }
+        }
+
+        if (mDimSkb) {
+            mPaint.setColor(0xa0000000);
+            canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
+        }
+
+        mDirtyRect.setEmpty();
+    }
+
+    private void drawSoftKey(Canvas canvas, SoftKey softKey, int keyXMargin,
+            int keyYMargin) {
+        Drawable bg;
+        int textColor;
+        if (mKeyPressed && softKey == mSoftKeyDown) {
+            bg = softKey.getKeyHlBg();
+            textColor = softKey.getColorHl();
+        } else {
+            bg = softKey.getKeyBg();
+            textColor = softKey.getColor();
+        }
+
+        if (null != bg) {
+            bg.setBounds(softKey.mLeft + keyXMargin, softKey.mTop + keyYMargin,
+                    softKey.mRight - keyXMargin, softKey.mBottom - keyYMargin);
+            bg.draw(canvas);
+        }
+
+        String keyLabel = softKey.getKeyLabel();
+        Drawable keyIcon = softKey.getKeyIcon();
+        if (null != keyIcon) {
+            Drawable icon = keyIcon;
+            int marginLeft = (softKey.width() - icon.getIntrinsicWidth()) / 2;
+            int marginRight = softKey.width() - icon.getIntrinsicWidth()
+                    - marginLeft;
+            int marginTop = (softKey.height() - icon.getIntrinsicHeight()) / 2;
+            int marginBottom = softKey.height() - icon.getIntrinsicHeight()
+                    - marginTop;
+            icon.setBounds(softKey.mLeft + marginLeft,
+                    softKey.mTop + marginTop, softKey.mRight - marginRight,
+                    softKey.mBottom - marginBottom);
+            icon.draw(canvas);
+        } else if (null != keyLabel) {
+            mPaint.setColor(textColor);
+            float x = softKey.mLeft
+                    + (softKey.width() - mPaint.measureText(keyLabel)) / 2.0f;
+            int fontHeight = mFmi.bottom - mFmi.top;
+            float marginY = (softKey.height() - fontHeight) / 2.0f;
+            float y = softKey.mTop + marginY - mFmi.top + mFmi.bottom / 1.5f;
+            canvas.drawText(keyLabel, x, y + 1, mPaint);
+        }
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/SoundManager.java b/PinyinIME/src/com/android/inputmethod/pinyin/SoundManager.java
new file mode 100644
index 0000000..82be407
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/SoundManager.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+/**
+ * Class used to manage related sound resources.
+ */
+public class SoundManager {
+    private static SoundManager mInstance = null;
+    private Context mContext;
+    private AudioManager mAudioManager;
+    private final float FX_VOLUME = 1.0f;
+    private boolean mSilentMode;
+
+    private SoundManager(Context context) {
+        mContext = context;
+        updateRingerMode();
+    }
+
+    public void updateRingerMode() {
+        if (mAudioManager == null) {
+            mAudioManager = (AudioManager) mContext
+                    .getSystemService(Context.AUDIO_SERVICE);
+        }
+        mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
+    }
+
+    public static SoundManager getInstance(Context context) {
+        if (null == mInstance) {
+            if (null != context) {
+                mInstance = new SoundManager(context);
+            }
+        }
+        return mInstance;
+    }
+
+    public void playKeyDown() {
+        if (mAudioManager == null) {
+            updateRingerMode();
+        }
+        if (!mSilentMode) {
+            int sound = AudioManager.FX_KEYPRESS_STANDARD;
+            mAudioManager.playSoundEffect(sound, FX_VOLUME);
+        }
+    }
+}
diff --git a/PinyinIME/src/com/android/inputmethod/pinyin/XmlKeyboardLoader.java b/PinyinIME/src/com/android/inputmethod/pinyin/XmlKeyboardLoader.java
new file mode 100644
index 0000000..fd192a3
--- /dev/null
+++ b/PinyinIME/src/com/android/inputmethod/pinyin/XmlKeyboardLoader.java
@@ -0,0 +1,835 @@
+/*
+ * 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.
+ */
+
+package com.android.inputmethod.pinyin;
+
+import com.android.inputmethod.pinyin.SoftKeyboard.KeyRow;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Class used to load a soft keyboard or a soft keyboard template from xml
+ * files.
+ */
+public class XmlKeyboardLoader {
+    /**
+     * The tag used to define an xml-based soft keyboard template.
+     */
+    private static final String XMLTAG_SKB_TEMPLATE = "skb_template";
+
+    /**
+     * The tag used to indicate the soft key type which is defined inside the
+     * {@link #XMLTAG_SKB_TEMPLATE} element in the xml file. file.
+     */
+    private static final String XMLTAG_KEYTYPE = "key_type";
+
+    /**
+     * The tag used to define a default key icon for enter/delete/space keys. It
+     * is defined inside the {@link #XMLTAG_SKB_TEMPLATE} element in the xml
+     * file.
+     */
+    private static final String XMLTAG_KEYICON = "key_icon";
+
+    /**
+     * Attribute tag of the left and right margin for a key. A key's width
+     * should be larger than double of this value. Defined inside
+     * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.
+     */
+    private static final String XMLATTR_KEY_XMARGIN = "key_xmargin";
+
+    /**
+     * Attribute tag of the top and bottom margin for a key. A key's height
+     * should be larger than double of this value. Defined inside
+     * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.
+     */
+    private static final String XMLATTR_KEY_YMARGIN = "key_ymargin";
+
+    /**
+     * Attribute tag of the keyboard background image. Defined inside
+     * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.
+     */
+    private static final String XMLATTR_SKB_BG = "skb_bg";
+
+    /**
+     * Attribute tag of the balloon background image for key press. Defined
+     * inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYBOARD}.
+     */
+    private static final String XMLATTR_BALLOON_BG = "balloon_bg";
+
+    /**
+     * Attribute tag of the popup balloon background image for key press or
+     * popup mini keyboard. Defined inside {@link #XMLTAG_SKB_TEMPLATE} and
+     * {@link #XMLTAG_KEYBOARD}.
+     */
+    private static final String XMLATTR_POPUP_BG = "popup_bg";
+
+    /**
+     * Attribute tag of the color to draw key label. Defined inside
+     * {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.
+     */
+    private static final String XMLATTR_COLOR = "color";
+
+    /**
+     * Attribute tag of the color to draw key's highlighted label. Defined
+     * inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.
+     */
+    private static final String XMLATTR_COLOR_HIGHLIGHT = "color_highlight";
+
+    /**
+     * Attribute tag of the color to draw key's label in the popup balloon.
+     * Defined inside {@link #XMLTAG_SKB_TEMPLATE} and {@link #XMLTAG_KEYTYPE}.
+     */
+    private static final String XMLATTR_COLOR_BALLOON = "color_balloon";
+
+    /**
+     * Attribute tag of the id of {@link #XMLTAG_KEYTYPE} and
+     * {@link #XMLTAG_KEY}. Key types and keys defined in a soft keyboard
+     * template should have id, because a soft keyboard needs the id to refer to
+     * these default definitions. If a key defined in {@link #XMLTAG_KEYBOARD}
+     * does not id, that means the key is newly defined; if it has id (and only
+     * has id), the id is used to find the default definition from the soft
+     * keyboard template.
+     */
+    private static final String XMLATTR_ID = "id";
+
+    /**
+     * Attribute tag of the key background for a specified key type. Defined
+     * inside {@link #XMLTAG_KEYTYPE}.
+     */
+    private static final String XMLATTR_KEYTYPE_BG = "bg";
+
+    /**
+     * Attribute tag of the key high-light background for a specified key type.
+     * Defined inside {@link #XMLTAG_KEYTYPE}.
+     */
+    private static final String XMLATTR_KEYTYPE_HLBG = "hlbg";
+
+    /**
+     * Attribute tag of the starting x-position of an element. It can be defined
+     * in {@link #XMLTAG_ROW} and {@link #XMLTAG_KEY} in {XMLTAG_SKB_TEMPLATE}.
+     * If not defined, 0 will be used. For a key defined in
+     * {@link #XMLTAG_KEYBOARD}, it always use its previous keys information to
+     * calculate its own position.
+     */
+    private static final String XMLATTR_START_POS_X = "start_pos_x";
+
+    /**
+     * Attribute tag of the starting y-position of an element. It can be defined
+     * in {@link #XMLTAG_ROW} and {@link #XMLTAG_KEY} in {XMLTAG_SKB_TEMPLATE}.
+     * If not defined, 0 will be used. For a key defined in
+     * {@link #XMLTAG_KEYBOARD}, it always use its previous keys information to
+     * calculate its own position.
+     */
+    private static final String XMLATTR_START_POS_Y = "start_pos_y";
+
+    /**
+     * Attribute tag of a row's id. Defined {@link #XMLTAG_ROW}. If not defined,
+     * -1 will be used. Rows with id -1 will be enabled always, rows with same
+     * row id will be enabled when the id is the same to the activated id of the
+     * soft keyboard.
+     */
+    private static final String XMLATTR_ROW_ID = "row_id";
+
+    /** The tag used to indicate the keyboard element in the xml file. */
+    private static final String XMLTAG_KEYBOARD = "keyboard";
+
+    /** The tag used to indicate the row element in the xml file. */
+    private static final String XMLTAG_ROW = "row";
+
+    /** The tag used to indicate key-array element in the xml file. */
+    private static final String XMLTAG_KEYS = "keys";
+
+    /**
+     * The tag used to indicate a key element in the xml file. If the element is
+     * defined in a soft keyboard template, it should have an id. If it is
+     * defined in a soft keyboard, id is not required.
+     */
+    private static final String XMLTAG_KEY = "key";
+
+    /** The tag used to indicate a key's toggle element in the xml file. */
+    private static final String XMLTAG_TOGGLE_STATE = "toggle_state";
+
+    /**
+     * Attribute tag of the toggle state id for toggle key. Defined inside
+     * {@link #XMLTAG_TOGGLE_STATE}
+     */
+    private static final String XMLATTR_TOGGLE_STATE_ID = "state_id";
+
+    /** Attribute tag of key template for the soft keyboard. */
+    private static final String XMLATTR_SKB_TEMPLATE = "skb_template";
+
+    /**
+     * Attribute tag used to indicate whether this soft keyboard needs to be
+     * cached in memory for future use. {@link #DEFAULT_SKB_CACHE_FLAG}
+     * specifies the default value.
+     */
+    private static final String XMLATTR_SKB_CACHE_FLAG = "skb_cache_flag";
+
+    /**
+     * Attribute tag used to indicate whether this soft keyboard is sticky. A
+     * sticky soft keyboard will keep the current layout unless user makes a
+     * switch explicitly. A none sticky soft keyboard will automatically goes
+     * back to the previous keyboard after click a none-function key.
+     * {@link #DEFAULT_SKB_STICKY_FLAG} specifies the default value.
+     */
+    private static final String XMLATTR_SKB_STICKY_FLAG = "skb_sticky_flag";
+
+    /** Attribute tag to indicate whether it is a QWERTY soft keyboard. */
+    private static final String XMLATTR_QWERTY = "qwerty";
+
+    /**
+     * When the soft keyboard is a QWERTY one, this attribute tag to get the
+     * information that whether it is defined in upper case.
+     */
+    private static final String XMLATTR_QWERTY_UPPERCASE = "qwerty_uppercase";
+
+    /** Attribute tag of key type. */
+    private static final String XMLATTR_KEY_TYPE = "key_type";
+
+    /** Attribute tag of key width. */
+    private static final String XMLATTR_KEY_WIDTH = "width";
+
+    /** Attribute tag of key height. */
+    private static final String XMLATTR_KEY_HEIGHT = "height";
+
+    /** Attribute tag of the key's repeating ability. */
+    private static final String XMLATTR_KEY_REPEAT = "repeat";
+
+    /** Attribute tag of the key's behavior for balloon. */
+    private static final String XMLATTR_KEY_BALLOON = "balloon";
+
+    /** Attribute tag of the key splitter in a key array. */
+    private static final String XMLATTR_KEY_SPLITTER = "splitter";
+
+    /** Attribute tag of the key labels in a key array. */
+    private static final String XMLATTR_KEY_LABELS = "labels";
+
+    /** Attribute tag of the key codes in a key array. */
+    private static final String XMLATTR_KEY_CODES = "codes";
+
+    /** Attribute tag of the key label in a key. */
+    private static final String XMLATTR_KEY_LABEL = "label";
+
+    /** Attribute tag of the key code in a key. */
+    private static final String XMLATTR_KEY_CODE = "code";
+
+    /** Attribute tag of the key icon in a key. */
+    private static final String XMLATTR_KEY_ICON = "icon";
+
+    /** Attribute tag of the key's popup icon in a key. */
+    private static final String XMLATTR_KEY_ICON_POPUP = "icon_popup";
+
+    /** The id for a mini popup soft keyboard. */
+    private static final String XMLATTR_KEY_POPUP_SKBID = "popup_skb";
+
+    private static boolean DEFAULT_SKB_CACHE_FLAG = true;
+
+    private static boolean DEFAULT_SKB_STICKY_FLAG = true;
+
+    /**
+     * The key type id for invalid key type. It is also used to generate next
+     * valid key type id by adding 1.
+     */
+    private static final int KEYTYPE_ID_LAST = -1;
+
+    private Context mContext;
+
+    private Resources mResources;
+
+    /** The event type in parsing the xml file. */
+    private int mXmlEventType;
+
+    /**
+     * The current soft keyboard template used by the current soft keyboard
+     * under loading.
+     **/
+    private SkbTemplate mSkbTemplate;
+
+    /** The x position for the next key. */
+    float mKeyXPos;
+
+    /** The y position for the next key. */
+    float mKeyYPos;
+
+    /** The width of the keyboard to load. */
+    int mSkbWidth;
+
+    /** The height of the keyboard to load. */
+    int mSkbHeight;
+
+    /** Key margin in x-way. */
+    float mKeyXMargin = 0;
+
+    /** Key margin in y-way. */
+    float mKeyYMargin = 0;
+
+    /**
+     * Used to indicate whether next event has been fetched during processing
+     * the the current event.
+     */
+    boolean mNextEventFetched = false;
+
+    String mAttrTmp;
+
+    class KeyCommonAttributes {
+        XmlResourceParser mXrp;
+        int keyType;
+        float keyWidth;
+        float keyHeight;
+        boolean repeat;
+        boolean balloon;
+
+        KeyCommonAttributes(XmlResourceParser xrp) {
+            mXrp = xrp;
+            balloon = true;
+        }
+
+        // Make sure the default object is not null.
+        boolean getAttributes(KeyCommonAttributes defAttr) {
+            keyType = getInteger(mXrp, XMLATTR_KEY_TYPE, defAttr.keyType);
+            keyWidth = getFloat(mXrp, XMLATTR_KEY_WIDTH, defAttr.keyWidth);
+            keyHeight = getFloat(mXrp, XMLATTR_KEY_HEIGHT, defAttr.keyHeight);
+            repeat = getBoolean(mXrp, XMLATTR_KEY_REPEAT, defAttr.repeat);
+            balloon = getBoolean(mXrp, XMLATTR_KEY_BALLOON, defAttr.balloon);
+            if (keyType < 0 || keyWidth <= 0 || keyHeight <= 0) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    public XmlKeyboardLoader(Context context) {
+        mContext = context;
+        mResources = mContext.getResources();
+    }
+
+    public SkbTemplate loadSkbTemplate(int resourceId) {
+        if (null == mContext || 0 == resourceId) {
+            return null;
+        }
+        Resources r = mResources;
+        XmlResourceParser xrp = r.getXml(resourceId);
+
+        KeyCommonAttributes attrDef = new KeyCommonAttributes(xrp);
+        KeyCommonAttributes attrKey = new KeyCommonAttributes(xrp);
+
+        mSkbTemplate = new SkbTemplate(resourceId);
+        int lastKeyTypeId = KEYTYPE_ID_LAST;
+        int globalColor = 0;
+        int globalColorHl = 0;
+        int globalColorBalloon = 0;
+        try {
+            mXmlEventType = xrp.next();
+            while (mXmlEventType != XmlResourceParser.END_DOCUMENT) {
+                mNextEventFetched = false;
+                if (mXmlEventType == XmlResourceParser.START_TAG) {
+                    String attribute = xrp.getName();
+                    if (XMLTAG_SKB_TEMPLATE.compareTo(attribute) == 0) {
+                        Drawable skbBg = getDrawable(xrp, XMLATTR_SKB_BG, null);
+                        Drawable balloonBg = getDrawable(xrp,
+                                XMLATTR_BALLOON_BG, null);
+                        Drawable popupBg = getDrawable(xrp, XMLATTR_POPUP_BG,
+                                null);
+                        if (null == skbBg || null == balloonBg
+                                || null == popupBg) {
+                            return null;
+                        }
+                        mSkbTemplate.setBackgrounds(skbBg, balloonBg, popupBg);
+
+                        float xMargin = getFloat(xrp, XMLATTR_KEY_XMARGIN, 0);
+                        float yMargin = getFloat(xrp, XMLATTR_KEY_YMARGIN, 0);
+                        mSkbTemplate.setMargins(xMargin, yMargin);
+
+                        // Get default global colors.
+                        globalColor = getColor(xrp, XMLATTR_COLOR, 0);
+                        globalColorHl = getColor(xrp, XMLATTR_COLOR_HIGHLIGHT,
+                                0xffffffff);
+                        globalColorBalloon = getColor(xrp,
+                                XMLATTR_COLOR_BALLOON, 0xffffffff);
+                    } else if (XMLTAG_KEYTYPE.compareTo(attribute) == 0) {
+                        int id = getInteger(xrp, XMLATTR_ID, KEYTYPE_ID_LAST);
+                        Drawable bg = getDrawable(xrp, XMLATTR_KEYTYPE_BG, null);
+                        Drawable hlBg = getDrawable(xrp, XMLATTR_KEYTYPE_HLBG,
+                                null);
+                        int color = getColor(xrp, XMLATTR_COLOR, globalColor);
+                        int colorHl = getColor(xrp, XMLATTR_COLOR_HIGHLIGHT,
+                                globalColorHl);
+                        int colorBalloon = getColor(xrp, XMLATTR_COLOR_BALLOON,
+                                globalColorBalloon);
+                        if (id != lastKeyTypeId + 1) {
+                            return null;
+                        }
+                        SoftKeyType keyType = mSkbTemplate.createKeyType(id,
+                                bg, hlBg);
+                        keyType.setColors(color, colorHl, colorBalloon);
+                        if (!mSkbTemplate.addKeyType(keyType)) {
+                            return null;
+                        }
+                        lastKeyTypeId = id;
+                    } else if (XMLTAG_KEYICON.compareTo(attribute) == 0) {
+                        int keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
+                        Drawable icon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
+                        Drawable iconPopup = getDrawable(xrp,
+                                XMLATTR_KEY_ICON_POPUP, null);
+                        if (null != icon && null != iconPopup) {
+                            mSkbTemplate.addDefaultKeyIcons(keyCode, icon,
+                                    iconPopup);
+                        }
+                    } else if (XMLTAG_KEY.compareTo(attribute) == 0) {
+                        int keyId = this.getInteger(xrp, XMLATTR_ID, -1);
+                        if (-1 == keyId) return null;
+
+                        if (!attrKey.getAttributes(attrDef)) {
+                            return null;
+                        }
+
+                        // Update the key position for the key.
+                        mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);
+                        mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, 0);
+
+                        SoftKey softKey = getSoftKey(xrp, attrKey);
+                        if (null == softKey) return null;
+                        mSkbTemplate.addDefaultKey(keyId, softKey);
+                    }
+                }
+                // Get the next tag.
+                if (!mNextEventFetched) mXmlEventType = xrp.next();
+            }
+            xrp.close();
+            return mSkbTemplate;
+        } catch (XmlPullParserException e) {
+            // Log.e(TAG, "Ill-formatted keyboard template resource file");
+        } catch (IOException e) {
+            // Log.e(TAG, "Unable to keyboard template resource file");
+        }
+        return null;
+    }
+
+    public SoftKeyboard loadKeyboard(int resourceId, int skbWidth, int skbHeight) {
+        if (null == mContext) return null;
+        Resources r = mResources;
+        SkbPool skbPool = SkbPool.getInstance();
+        XmlResourceParser xrp = mContext.getResources().getXml(resourceId);
+        mSkbTemplate = null;
+        SoftKeyboard softKeyboard = null;
+        Drawable skbBg;
+        Drawable popupBg;
+        Drawable balloonBg;
+        SoftKey softKey = null;
+
+        KeyCommonAttributes attrDef = new KeyCommonAttributes(xrp);
+        KeyCommonAttributes attrSkb = new KeyCommonAttributes(xrp);
+        KeyCommonAttributes attrRow = new KeyCommonAttributes(xrp);
+        KeyCommonAttributes attrKeys = new KeyCommonAttributes(xrp);
+        KeyCommonAttributes attrKey = new KeyCommonAttributes(xrp);
+
+        mKeyXPos = 0;
+        mKeyYPos = 0;
+        mSkbWidth = skbWidth;
+        mSkbHeight = skbHeight;
+
+        try {
+            mKeyXMargin = 0;
+            mKeyYMargin = 0;
+            mXmlEventType = xrp.next();
+            while (mXmlEventType != XmlResourceParser.END_DOCUMENT) {
+                mNextEventFetched = false;
+                if (mXmlEventType == XmlResourceParser.START_TAG) {
+                    String attr = xrp.getName();
+                    // 1. Is it the root element, "keyboard"?
+                    if (XMLTAG_KEYBOARD.compareTo(attr) == 0) {
+                        // 1.1 Get the keyboard template id.
+                        int skbTemplateId = xrp.getAttributeResourceValue(null,
+                                XMLATTR_SKB_TEMPLATE, 0);
+
+                        // 1.2 Try to get the template from pool. If it is not
+                        // in, the pool will try to load it.
+                        mSkbTemplate = skbPool.getSkbTemplate(skbTemplateId,
+                                mContext);
+
+                        if (null == mSkbTemplate
+                                || !attrSkb.getAttributes(attrDef)) {
+                            return null;
+                        }
+
+                        boolean cacheFlag = getBoolean(xrp,
+                                XMLATTR_SKB_CACHE_FLAG, DEFAULT_SKB_CACHE_FLAG);
+                        boolean stickyFlag = getBoolean(xrp,
+                                XMLATTR_SKB_STICKY_FLAG,
+                                DEFAULT_SKB_STICKY_FLAG);
+                        boolean isQwerty = getBoolean(xrp, XMLATTR_QWERTY,
+                                false);
+                        boolean isQwertyUpperCase = getBoolean(xrp,
+                                XMLATTR_QWERTY_UPPERCASE, false);
+
+                        softKeyboard = new SoftKeyboard(resourceId,
+                                mSkbTemplate, mSkbWidth, mSkbHeight);
+                        softKeyboard.setFlags(cacheFlag, stickyFlag, isQwerty,
+                                isQwertyUpperCase);
+
+                        mKeyXMargin = getFloat(xrp, XMLATTR_KEY_XMARGIN,
+                                mSkbTemplate.getXMargin());
+                        mKeyYMargin = getFloat(xrp, XMLATTR_KEY_YMARGIN,
+                                mSkbTemplate.getYMargin());
+                        skbBg = getDrawable(xrp, XMLATTR_SKB_BG, null);
+                        popupBg = getDrawable(xrp, XMLATTR_POPUP_BG, null);
+                        balloonBg = getDrawable(xrp, XMLATTR_BALLOON_BG, null);
+                        if (null != skbBg) {
+                            softKeyboard.setSkbBackground(skbBg);
+                        }
+                        if (null != popupBg) {
+                            softKeyboard.setPopupBackground(popupBg);
+                        }
+                        if (null != balloonBg) {
+                            softKeyboard.setKeyBalloonBackground(balloonBg);
+                        }
+                        softKeyboard.setKeyMargins(mKeyXMargin, mKeyYMargin);
+                    } else if (XMLTAG_ROW.compareTo(attr) == 0) {
+                        if (!attrRow.getAttributes(attrSkb)) {
+                            return null;
+                        }
+                        // Get the starting positions for the row.
+                        mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);
+                        mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, mKeyYPos);
+                        int rowId = getInteger(xrp, XMLATTR_ROW_ID,
+                                KeyRow.ALWAYS_SHOW_ROW_ID);
+                        softKeyboard.beginNewRow(rowId, mKeyYPos);
+                    } else if (XMLTAG_KEYS.compareTo(attr) == 0) {
+                        if (null == softKeyboard) return null;
+                        if (!attrKeys.getAttributes(attrRow)) {
+                            return null;
+                        }
+
+                        String splitter = xrp.getAttributeValue(null,
+                                XMLATTR_KEY_SPLITTER);
+                        splitter = Pattern.quote(splitter);
+                        String labels = xrp.getAttributeValue(null,
+                                XMLATTR_KEY_LABELS);
+                        String codes = xrp.getAttributeValue(null,
+                                XMLATTR_KEY_CODES);
+                        if (null == splitter || null == labels) {
+                            return null;
+                        }
+                        String labelArr[] = labels.split(splitter);
+                        String codeArr[] = null;
+                        if (null != codes) {
+                            codeArr = codes.split(splitter);
+                            if (labelArr.length != codeArr.length) {
+                                return null;
+                            }
+                        }
+
+                        for (int i = 0; i < labelArr.length; i++) {
+                            softKey = new SoftKey();
+                            int keyCode = 0;
+                            if (null != codeArr) {
+                                keyCode = Integer.valueOf(codeArr[i]);
+                            }
+                            softKey.setKeyAttribute(keyCode, labelArr[i],
+                                    attrKeys.repeat, attrKeys.balloon);
+
+                            softKey.setKeyType(mSkbTemplate
+                                    .getKeyType(attrKeys.keyType), null, null);
+
+                            float left, right, top, bottom;
+                            left = mKeyXPos;
+
+                            right = left + attrKeys.keyWidth;
+                            top = mKeyYPos;
+                            bottom = top + attrKeys.keyHeight;
+
+                            if (right - left < 2 * mKeyXMargin) return null;
+                            if (bottom - top < 2 * mKeyYMargin) return null;
+
+                            softKey.setKeyDimensions(left, top, right, bottom);
+                            softKeyboard.addSoftKey(softKey);
+                            mKeyXPos = right;
+                            if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {
+                                return null;
+                            }
+                        }
+                    } else if (XMLTAG_KEY.compareTo(attr) == 0) {
+                        if (null == softKeyboard) {
+                            return null;
+                        }
+                        if (!attrKey.getAttributes(attrRow)) {
+                            return null;
+                        }
+
+                        int keyId = this.getInteger(xrp, XMLATTR_ID, -1);
+                        if (keyId >= 0) {
+                            softKey = mSkbTemplate.getDefaultKey(keyId);
+                        } else {
+                            softKey = getSoftKey(xrp, attrKey);
+                        }
+                        if (null == softKey) return null;
+
+                        // Update the position for next key.
+                        mKeyXPos = softKey.mRightF;
+                        if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {
+                            return null;
+                        }
+                        // If the current xml event type becomes a starting tag,
+                        // it indicates that we have parsed too much to get
+                        // toggling states, and we started a new row. In this
+                        // case, the row starting position information should
+                        // be updated.
+                        if (mXmlEventType == XmlResourceParser.START_TAG) {
+                            attr = xrp.getName();
+                            if (XMLTAG_ROW.compareTo(attr) == 0) {
+                                mKeyYPos += attrRow.keyHeight;
+                                if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {
+                                    return null;
+                                }
+                            }
+                        }
+                        softKeyboard.addSoftKey(softKey);
+                    }
+                } else if (mXmlEventType == XmlResourceParser.END_TAG) {
+                    String attr = xrp.getName();
+                    if (XMLTAG_ROW.compareTo(attr) == 0) {
+                        mKeyYPos += attrRow.keyHeight;
+                        if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {
+                            return null;
+                        }
+                    }
+                }
+
+                // Get the next tag.
+                if (!mNextEventFetched) mXmlEventType = xrp.next();
+            }
+            xrp.close();
+            softKeyboard.setSkbCoreSize(mSkbWidth, mSkbHeight);
+            return softKeyboard;
+        } catch (XmlPullParserException e) {
+            // Log.e(TAG, "Ill-formatted keybaord resource file");
+        } catch (IOException e) {
+            // Log.e(TAG, "Unable to read keyboard resource file");
+        }
+        return null;
+    }
+
+    // Caller makes sure xrp and r are valid.
+    private SoftKey getSoftKey(XmlResourceParser xrp,
+            KeyCommonAttributes attrKey) throws XmlPullParserException,
+            IOException {
+        int keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
+        String keyLabel = getString(xrp, XMLATTR_KEY_LABEL, null);
+        Drawable keyIcon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
+        Drawable keyIconPopup = getDrawable(xrp, XMLATTR_KEY_ICON_POPUP, null);
+        int popupSkbId = xrp.getAttributeResourceValue(null,
+                XMLATTR_KEY_POPUP_SKBID, 0);
+
+        if (null == keyLabel && null == keyIcon) {
+            keyIcon = mSkbTemplate.getDefaultKeyIcon(keyCode);
+            keyIconPopup = mSkbTemplate.getDefaultKeyIconPopup(keyCode);
+            if (null == keyIcon || null == keyIconPopup) return null;
+        }
+
+        // Dimension information must been initialized before
+        // getting toggle state, because mKeyYPos may be changed
+        // to next row when trying to get toggle state.
+        float left, right, top, bottom;
+        left = mKeyXPos;
+        right = left + attrKey.keyWidth;
+        top = mKeyYPos;
+        bottom = top + attrKey.keyHeight;
+
+        if (right - left < 2 * mKeyXMargin) return null;
+        if (bottom - top < 2 * mKeyYMargin) return null;
+
+        // Try to find if the next tag is
+        // {@link #XMLTAG_TOGGLE_STATE_OF_KEY}, if yes, try to
+        // create a toggle key.
+        boolean toggleKey = false;
+        mXmlEventType = xrp.next();
+        mNextEventFetched = true;
+
+        SoftKey softKey;
+        if (mXmlEventType == XmlResourceParser.START_TAG) {
+            mAttrTmp = xrp.getName();
+            if (mAttrTmp.compareTo(XMLTAG_TOGGLE_STATE) == 0) {
+                toggleKey = true;
+            }
+        }
+        if (toggleKey) {
+            softKey = new SoftKeyToggle();
+            if (!((SoftKeyToggle) softKey).setToggleStates(getToggleStates(
+                    attrKey, (SoftKeyToggle) softKey, keyCode))) {
+                return null;
+            }
+        } else {
+            softKey = new SoftKey();
+        }
+
+        // Set the normal state
+        softKey.setKeyAttribute(keyCode, keyLabel, attrKey.repeat,
+                attrKey.balloon);
+        softKey.setPopupSkbId(popupSkbId);
+        softKey.setKeyType(mSkbTemplate.getKeyType(attrKey.keyType), keyIcon,
+                keyIconPopup);
+
+        softKey.setKeyDimensions(left, top, right, bottom);
+        return softKey;
+    }
+
+    private SoftKeyToggle.ToggleState getToggleStates(
+            KeyCommonAttributes attrKey, SoftKeyToggle softKey, int defKeyCode)
+            throws XmlPullParserException, IOException {
+        XmlResourceParser xrp = attrKey.mXrp;
+        int stateId = getInteger(xrp, XMLATTR_TOGGLE_STATE_ID, 0);
+        if (0 == stateId) return null;
+
+        String keyLabel = getString(xrp, XMLATTR_KEY_LABEL, null);
+        int keyTypeId = getInteger(xrp, XMLATTR_KEY_TYPE, KEYTYPE_ID_LAST);
+        int keyCode;
+        if (null == keyLabel) {
+            keyCode = getInteger(xrp, XMLATTR_KEY_CODE, defKeyCode);
+        } else {
+            keyCode = getInteger(xrp, XMLATTR_KEY_CODE, 0);
+        }
+        Drawable icon = getDrawable(xrp, XMLATTR_KEY_ICON, null);
+        Drawable iconPopup = getDrawable(xrp, XMLATTR_KEY_ICON_POPUP, null);
+        if (null == icon && null == keyLabel) {
+            return null;
+        }
+        SoftKeyToggle.ToggleState rootState = softKey.createToggleState();
+        rootState.setStateId(stateId);
+        rootState.mKeyType = null;
+        if (KEYTYPE_ID_LAST != keyTypeId) {
+            rootState.mKeyType = mSkbTemplate.getKeyType(keyTypeId);
+        }
+        rootState.mKeyCode = keyCode;
+        rootState.mKeyIcon = icon;
+        rootState.mKeyIconPopup = iconPopup;
+        rootState.mKeyLabel = keyLabel;
+
+        boolean repeat = getBoolean(xrp, XMLATTR_KEY_REPEAT, attrKey.repeat);
+        boolean balloon = getBoolean(xrp, XMLATTR_KEY_BALLOON, attrKey.balloon);
+        rootState.setStateFlags(repeat, balloon);
+
+        rootState.mNextState = null;
+
+        // If there is another toggle state.
+        mXmlEventType = xrp.next();
+        while (mXmlEventType != XmlResourceParser.START_TAG
+                && mXmlEventType != XmlResourceParser.END_DOCUMENT) {
+            mXmlEventType = xrp.next();
+        }
+        if (mXmlEventType == XmlResourceParser.START_TAG) {
+            String attr = xrp.getName();
+            if (attr.compareTo(XMLTAG_TOGGLE_STATE) == 0) {
+                SoftKeyToggle.ToggleState nextState = getToggleStates(attrKey,
+                        softKey, defKeyCode);
+                if (null == nextState) return null;
+                rootState.mNextState = nextState;
+            }
+        }
+
+        return rootState;
+    }
+
+    private int getInteger(XmlResourceParser xrp, String name, int defValue) {
+        int resId = xrp.getAttributeResourceValue(null, name, 0);
+        String s;
+        if (resId == 0) {
+            s = xrp.getAttributeValue(null, name);
+            if (null == s) return defValue;
+            try {
+                int ret = Integer.valueOf(s);
+                return ret;
+            } catch (NumberFormatException e) {
+                return defValue;
+            }
+        } else {
+            return Integer.parseInt(mContext.getResources().getString(resId));
+        }
+    }
+
+    private int getColor(XmlResourceParser xrp, String name, int defValue) {
+        int resId = xrp.getAttributeResourceValue(null, name, 0);
+        String s;
+        if (resId == 0) {
+            s = xrp.getAttributeValue(null, name);
+            if (null == s) return defValue;
+            try {
+                int ret = Integer.valueOf(s);
+                return ret;
+            } catch (NumberFormatException e) {
+                return defValue;
+            }
+        } else {
+            return mContext.getResources().getColor(resId);
+        }
+    }
+
+    private String getString(XmlResourceParser xrp, String name, String defValue) {
+        int resId = xrp.getAttributeResourceValue(null, name, 0);
+        if (resId == 0) {
+            return xrp.getAttributeValue(null, name);
+        } else {
+            return mContext.getResources().getString(resId);
+        }
+    }
+
+    private float getFloat(XmlResourceParser xrp, String name, float defValue) {
+        int resId = xrp.getAttributeResourceValue(null, name, 0);
+        if (resId == 0) {
+            String s = xrp.getAttributeValue(null, name);
+            if (null == s) return defValue;
+            try {
+                float ret;
+                if (s.endsWith("%p")) {
+                    ret = Float.parseFloat(s.substring(0, s.length() - 2)) / 100;
+                } else {
+                    ret = Float.parseFloat(s);
+                }
+                return ret;
+            } catch (NumberFormatException e) {
+                return defValue;
+            }
+        } else {
+            return mContext.getResources().getDimension(resId);
+        }
+    }
+
+    private boolean getBoolean(XmlResourceParser xrp, String name,
+            boolean defValue) {
+        String s = xrp.getAttributeValue(null, name);
+        if (null == s) return defValue;
+        try {
+            boolean ret = Boolean.parseBoolean(s);
+            return ret;
+        } catch (NumberFormatException e) {
+            return defValue;
+        }
+    }
+
+    private Drawable getDrawable(XmlResourceParser xrp, String name,
+            Drawable defValue) {
+        int resId = xrp.getAttributeResourceValue(null, name, 0);
+        if (0 == resId) return defValue;
+        return mResources.getDrawable(resId);
+    }
+}