From 923bf41f853a544fd0d71fbf7dc90359ec359812 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Fri, 13 Mar 2009 15:11:42 -0700 Subject: [PATCH] auto import from //branches/cupcake/...@138744 --- Android.mk | 17 + AndroidManifest.xml | 24 + dictionaries/sample.xml | 16 + dictionary/Android.mk | 28 + ...oid_inputmethod_latin_BinaryDictionary.cpp | 207 ++++ dictionary/src/basechars.h | 172 +++ dictionary/src/dictionary.cpp | 277 +++++ dictionary/src/dictionary.h | 69 ++ .../keyboard_suggest_strip_divider.png | Bin 0 -> 166 bytes .../candidate_feedback_background.9.png | Bin 0 -> 1182 bytes res/drawable/dialog_bubble_step02.9.png | Bin 0 -> 1036 bytes res/drawable/dialog_bubble_step03.9.png | Bin 0 -> 1692 bytes res/drawable/dialog_bubble_step04.9.png | Bin 0 -> 1957 bytes res/drawable/highlight_pressed.png | Bin 0 -> 593 bytes res/drawable/ic_dialog_keyboard.png | Bin 0 -> 528 bytes res/drawable/ic_suggest_scroll_background.xml | 25 + .../ic_suggest_strip_scroll_left_arrow.png | Bin 0 -> 445 bytes .../ic_suggest_strip_scroll_right_arrow.png | Bin 0 -> 440 bytes res/drawable/keyboard_suggest_strip.9.png | Bin 0 -> 220 bytes .../keyboard_suggest_strip_divider.png | Bin 0 -> 166 bytes res/drawable/sym_keyboard_delete.png | Bin 0 -> 1366 bytes res/drawable/sym_keyboard_done.png | Bin 0 -> 771 bytes res/drawable/sym_keyboard_feedback_delete.png | Bin 0 -> 524 bytes res/drawable/sym_keyboard_feedback_done.png | Bin 0 -> 498 bytes res/drawable/sym_keyboard_feedback_numalt.png | Bin 0 -> 735 bytes .../sym_keyboard_feedback_numpound.png | Bin 0 -> 478 bytes .../sym_keyboard_feedback_numstar.png | Bin 0 -> 464 bytes res/drawable/sym_keyboard_feedback_return.png | Bin 0 -> 381 bytes res/drawable/sym_keyboard_feedback_search.png | Bin 0 -> 501 bytes res/drawable/sym_keyboard_feedback_shift.png | Bin 0 -> 437 bytes .../sym_keyboard_feedback_shift_locked.png | Bin 0 -> 333 bytes res/drawable/sym_keyboard_feedback_space.png | Bin 0 -> 223 bytes res/drawable/sym_keyboard_num0.png | Bin 0 -> 1160 bytes res/drawable/sym_keyboard_num1.png | Bin 0 -> 506 bytes res/drawable/sym_keyboard_num2.png | Bin 0 -> 1778 bytes res/drawable/sym_keyboard_num3.png | Bin 0 -> 1676 bytes res/drawable/sym_keyboard_num4.png | Bin 0 -> 1540 bytes res/drawable/sym_keyboard_num5.png | Bin 0 -> 1417 bytes res/drawable/sym_keyboard_num6.png | Bin 0 -> 1952 bytes res/drawable/sym_keyboard_num7.png | Bin 0 -> 1997 bytes res/drawable/sym_keyboard_num8.png | Bin 0 -> 1605 bytes res/drawable/sym_keyboard_num9.png | Bin 0 -> 2173 bytes res/drawable/sym_keyboard_numalt.png | Bin 0 -> 1673 bytes res/drawable/sym_keyboard_numpound.png | Bin 0 -> 963 bytes res/drawable/sym_keyboard_numstar.png | Bin 0 -> 954 bytes res/drawable/sym_keyboard_return.png | Bin 0 -> 866 bytes res/drawable/sym_keyboard_search.png | Bin 0 -> 1029 bytes res/drawable/sym_keyboard_shift.png | Bin 0 -> 1017 bytes res/drawable/sym_keyboard_shift_locked.png | Bin 0 -> 799 bytes res/drawable/sym_keyboard_space.png | Bin 0 -> 424 bytes res/layout/bubble_text.xml | 27 + res/layout/candidate_preview.xml | 29 + res/layout/candidates.xml | 79 ++ res/layout/input.xml | 27 + res/raw/main.dict | Bin 0 -> 42 bytes res/raw/type3.ogg | Bin 0 -> 4126 bytes res/values-cs/strings.xml | 101 ++ res/values-de/strings.xml | 101 ++ res/values-en/bools.xml | 22 + res/values-es/strings.xml | 101 ++ res/values-fr/donottranslate.xml | 25 + res/values-fr/strings.xml | 101 ++ res/values-it/donottranslate.xml | 23 + res/values-it/strings.xml | 101 ++ res/values-ja/strings.xml | 101 ++ res/values-ko/strings.xml | 101 ++ res/values-land/dimens.xml | 23 + res/values-nb/strings.xml | 92 ++ res/values-nl/strings.xml | 101 ++ res/values-pl/strings.xml | 101 ++ res/values-ru/strings.xml | 101 ++ res/values-zh-rCN/strings.xml | 101 ++ res/values-zh-rTW/strings.xml | 101 ++ res/values/colors.xml | 24 + res/values/dimens.xml | 24 + res/values/donottranslate.xml | 25 + res/values/durations.xml | 25 + res/values/strings.xml | 142 +++ res/xml-de/kbd_qwerty.xml | 167 +++ res/xml-fr/kbd_qwerty.xml | 168 +++ res/xml/azerty.xml | 114 ++ res/xml/kbd_alpha.xml | 106 ++ res/xml/kbd_phone.xml | 67 + res/xml/kbd_phone_symbols.xml | 68 + res/xml/kbd_popup_template.xml | 27 + res/xml/kbd_qwerty.xml | 166 +++ res/xml/kbd_symbols.xml | 138 +++ res/xml/kbd_symbols_shift.xml | 93 ++ res/xml/method.xml | 26 + res/xml/popup_domains.xml | 34 + res/xml/popup_punctuation.xml | 37 + res/xml/popup_smileys.xml | 49 + res/xml/prefs.xml | 81 ++ .../inputmethod/latin/BinaryDictionary.java | 127 ++ .../inputmethod/latin/CandidateView.java | 478 ++++++++ .../latin/CandidateViewContainer.java | 83 ++ .../android/inputmethod/latin/Dictionary.java | 89 ++ .../inputmethod/latin/KeyboardSwitcher.java | 241 ++++ .../android/inputmethod/latin/LatinIME.java | 1091 +++++++++++++++++ .../inputmethod/latin/LatinIMESettings.java | 75 ++ .../inputmethod/latin/LatinKeyboard.java | 228 ++++ .../inputmethod/latin/LatinKeyboardView.java | 180 +++ .../android/inputmethod/latin/Suggest.java | 278 +++++ .../inputmethod/latin/TextEntryState.java | 232 ++++ .../android/inputmethod/latin/Tutorial.java | 226 ++++ .../inputmethod/latin/UserDictionary.java | 473 +++++++ .../inputmethod/latin/WordComposer.java | 141 +++ 107 files changed, 7917 insertions(+) create mode 100755 Android.mk create mode 100755 AndroidManifest.xml create mode 100644 dictionaries/sample.xml create mode 100644 dictionary/Android.mk create mode 100644 dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp create mode 100644 dictionary/src/basechars.h create mode 100644 dictionary/src/dictionary.cpp create mode 100644 dictionary/src/dictionary.h create mode 100644 res/drawable-land/keyboard_suggest_strip_divider.png create mode 100644 res/drawable/candidate_feedback_background.9.png create mode 100755 res/drawable/dialog_bubble_step02.9.png create mode 100755 res/drawable/dialog_bubble_step03.9.png create mode 100755 res/drawable/dialog_bubble_step04.9.png create mode 100644 res/drawable/highlight_pressed.png create mode 100644 res/drawable/ic_dialog_keyboard.png create mode 100644 res/drawable/ic_suggest_scroll_background.xml create mode 100644 res/drawable/ic_suggest_strip_scroll_left_arrow.png create mode 100644 res/drawable/ic_suggest_strip_scroll_right_arrow.png create mode 100644 res/drawable/keyboard_suggest_strip.9.png create mode 100644 res/drawable/keyboard_suggest_strip_divider.png create mode 100644 res/drawable/sym_keyboard_delete.png create mode 100755 res/drawable/sym_keyboard_done.png create mode 100644 res/drawable/sym_keyboard_feedback_delete.png create mode 100755 res/drawable/sym_keyboard_feedback_done.png create mode 100644 res/drawable/sym_keyboard_feedback_numalt.png create mode 100644 res/drawable/sym_keyboard_feedback_numpound.png create mode 100644 res/drawable/sym_keyboard_feedback_numstar.png create mode 100644 res/drawable/sym_keyboard_feedback_return.png create mode 100755 res/drawable/sym_keyboard_feedback_search.png create mode 100644 res/drawable/sym_keyboard_feedback_shift.png create mode 100755 res/drawable/sym_keyboard_feedback_shift_locked.png create mode 100644 res/drawable/sym_keyboard_feedback_space.png create mode 100644 res/drawable/sym_keyboard_num0.png create mode 100644 res/drawable/sym_keyboard_num1.png create mode 100644 res/drawable/sym_keyboard_num2.png create mode 100644 res/drawable/sym_keyboard_num3.png create mode 100644 res/drawable/sym_keyboard_num4.png create mode 100644 res/drawable/sym_keyboard_num5.png create mode 100644 res/drawable/sym_keyboard_num6.png create mode 100644 res/drawable/sym_keyboard_num7.png create mode 100644 res/drawable/sym_keyboard_num8.png create mode 100644 res/drawable/sym_keyboard_num9.png create mode 100644 res/drawable/sym_keyboard_numalt.png create mode 100644 res/drawable/sym_keyboard_numpound.png create mode 100644 res/drawable/sym_keyboard_numstar.png create mode 100644 res/drawable/sym_keyboard_return.png create mode 100755 res/drawable/sym_keyboard_search.png create mode 100644 res/drawable/sym_keyboard_shift.png create mode 100755 res/drawable/sym_keyboard_shift_locked.png create mode 100644 res/drawable/sym_keyboard_space.png create mode 100644 res/layout/bubble_text.xml create mode 100755 res/layout/candidate_preview.xml create mode 100755 res/layout/candidates.xml create mode 100755 res/layout/input.xml create mode 100755 res/raw/main.dict create mode 100755 res/raw/type3.ogg create mode 100644 res/values-cs/strings.xml create mode 100644 res/values-de/strings.xml create mode 100644 res/values-en/bools.xml create mode 100644 res/values-es/strings.xml create mode 100644 res/values-fr/donottranslate.xml create mode 100644 res/values-fr/strings.xml create mode 100644 res/values-it/donottranslate.xml create mode 100644 res/values-it/strings.xml create mode 100644 res/values-ja/strings.xml create mode 100644 res/values-ko/strings.xml create mode 100644 res/values-land/dimens.xml create mode 100644 res/values-nb/strings.xml create mode 100644 res/values-nl/strings.xml create mode 100644 res/values-pl/strings.xml create mode 100644 res/values-ru/strings.xml create mode 100644 res/values-zh-rCN/strings.xml create mode 100644 res/values-zh-rTW/strings.xml create mode 100644 res/values/colors.xml create mode 100644 res/values/dimens.xml create mode 100644 res/values/donottranslate.xml create mode 100644 res/values/durations.xml create mode 100644 res/values/strings.xml create mode 100755 res/xml-de/kbd_qwerty.xml create mode 100644 res/xml-fr/kbd_qwerty.xml create mode 100644 res/xml/azerty.xml create mode 100644 res/xml/kbd_alpha.xml create mode 100755 res/xml/kbd_phone.xml create mode 100755 res/xml/kbd_phone_symbols.xml create mode 100644 res/xml/kbd_popup_template.xml create mode 100755 res/xml/kbd_qwerty.xml create mode 100755 res/xml/kbd_symbols.xml create mode 100755 res/xml/kbd_symbols_shift.xml create mode 100644 res/xml/method.xml create mode 100644 res/xml/popup_domains.xml create mode 100644 res/xml/popup_punctuation.xml create mode 100644 res/xml/popup_smileys.xml create mode 100644 res/xml/prefs.xml create mode 100644 src/com/android/inputmethod/latin/BinaryDictionary.java create mode 100755 src/com/android/inputmethod/latin/CandidateView.java create mode 100644 src/com/android/inputmethod/latin/CandidateViewContainer.java create mode 100644 src/com/android/inputmethod/latin/Dictionary.java create mode 100644 src/com/android/inputmethod/latin/KeyboardSwitcher.java create mode 100644 src/com/android/inputmethod/latin/LatinIME.java create mode 100644 src/com/android/inputmethod/latin/LatinIMESettings.java create mode 100644 src/com/android/inputmethod/latin/LatinKeyboard.java create mode 100644 src/com/android/inputmethod/latin/LatinKeyboardView.java create mode 100755 src/com/android/inputmethod/latin/Suggest.java create mode 100644 src/com/android/inputmethod/latin/TextEntryState.java create mode 100644 src/com/android/inputmethod/latin/Tutorial.java create mode 100644 src/com/android/inputmethod/latin/UserDictionary.java create mode 100644 src/com/android/inputmethod/latin/WordComposer.java diff --git a/Android.mk b/Android.mk new file mode 100755 index 000000000..e3215e822 --- /dev/null +++ b/Android.mk @@ -0,0 +1,17 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := user + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := LatinIME + +LOCAL_CERTIFICATE := shared + +LOCAL_JNI_SHARED_LIBRARIES := libjni_latinime + +LOCAL_AAPT_FLAGS := -0 .dict + +include $(BUILD_PACKAGE) +include $(LOCAL_PATH)/dictionary/Android.mk diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100755 index 000000000..89cdad2ca --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/dictionaries/sample.xml b/dictionaries/sample.xml new file mode 100644 index 000000000..85233b63a --- /dev/null +++ b/dictionaries/sample.xml @@ -0,0 +1,16 @@ + + + this + is + sample + wordlist + + diff --git a/dictionary/Android.mk b/dictionary/Android.mk new file mode 100644 index 000000000..9ba9f75ec --- /dev/null +++ b/dictionary/Android.mk @@ -0,0 +1,28 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_C_INCLUDES += $(LOCAL_PATH)/src + +LOCAL_SRC_FILES := \ + jni/com_android_inputmethod_latin_BinaryDictionary.cpp \ + src/dictionary.cpp + +LOCAL_C_INCLUDES += \ + external/icu4c/common \ + $(JNI_H_INCLUDE) + +LOCAL_LDLIBS := -lm + +LOCAL_PRELINK_MODULE := false + +LOCAL_SHARED_LIBRARIES := \ + libandroid_runtime \ + libcutils \ + libutils \ + libicuuc + +LOCAL_MODULE := libjni_latinime + +LOCAL_MODULE_TAGS := user + +include $(BUILD_SHARED_LIBRARY) diff --git a/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp new file mode 100644 index 000000000..65c640b40 --- /dev/null +++ b/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -0,0 +1,207 @@ +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "BinaryDictionary" +#include "utils/Log.h" + +#include +#include +#include +#include + +#include +#include "utils/AssetManager.h" +#include "utils/Asset.h" + +#include "dictionary.h" + +// ---------------------------------------------------------------------------- + +using namespace latinime; + +using namespace android; + +static jfieldID sDescriptorField; +static jfieldID sAssetManagerNativeField; +static jmethodID sAddWordMethod; + +// +// helper function to throw an exception +// +static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) +{ + if (jclass cls = env->FindClass(ex)) { + char msg[1000]; + sprintf(msg, fmt, data); + env->ThrowNew(cls, msg); + env->DeleteLocalRef(cls); + } +} + +static jint latinime_BinaryDictionary_open + (JNIEnv *env, jobject object, jobject assetManager, jstring resourceString, + jint typedLetterMultiplier, jint fullWordMultiplier) +{ + // Get the native file descriptor from the FileDescriptor object + AssetManager *am = (AssetManager*) env->GetIntField(assetManager, sAssetManagerNativeField); + if (!am) { + LOGE("DICT: Couldn't get AssetManager native peer\n"); + return 0; + } + const char *resourcePath = env->GetStringUTFChars(resourceString, NULL); + + Asset *dictAsset = am->openNonAsset(resourcePath, Asset::ACCESS_BUFFER); + if (dictAsset == NULL) { + LOGE("DICT: Couldn't get asset %s\n", resourcePath); + env->ReleaseStringUTFChars(resourceString, resourcePath); + return 0; + } + + void *dict = (void*) dictAsset->getBuffer(false); + if (dict == NULL) { + LOGE("DICT: Dictionary buffer is null\n"); + env->ReleaseStringUTFChars(resourceString, resourcePath); + return 0; + } + Dictionary *dictionary = new Dictionary(dict, typedLetterMultiplier, fullWordMultiplier); + dictionary->setAsset(dictAsset); + + env->ReleaseStringUTFChars(resourceString, resourcePath); + return (jint) dictionary; +} + +static int latinime_BinaryDictionary_getSuggestions( + JNIEnv *env, jobject object, jint dict, jintArray inputArray, jint arraySize, + jcharArray outputArray, jintArray frequencyArray, jint maxWordLength, jint maxWords, + jint maxAlternatives) +{ + Dictionary *dictionary = (Dictionary*) dict; + if (dictionary == NULL) + return 0; + + int *frequencies = env->GetIntArrayElements(frequencyArray, NULL); + int *inputCodes = env->GetIntArrayElements(inputArray, NULL); + jchar *outputChars = env->GetCharArrayElements(outputArray, NULL); + + int count = dictionary->getSuggestions(inputCodes, arraySize, (unsigned short*) outputChars, frequencies, + maxWordLength, maxWords, maxAlternatives); + + env->ReleaseIntArrayElements(frequencyArray, frequencies, JNI_COMMIT); + env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT); + env->ReleaseCharArrayElements(outputArray, outputChars, JNI_COMMIT); + + return count; +} + +static jboolean latinime_BinaryDictionary_isValidWord + (JNIEnv *env, jobject object, jint dict, jcharArray wordArray, jint wordLength) +{ + Dictionary *dictionary = (Dictionary*) dict; + if (dictionary == NULL) return (jboolean) false; + + jchar *word = env->GetCharArrayElements(wordArray, NULL); + jboolean result = dictionary->isValidWord((unsigned short*) word, wordLength); + env->ReleaseCharArrayElements(wordArray, word, JNI_ABORT); + + return result; +} + +static void latinime_BinaryDictionary_close + (JNIEnv *env, jobject object, jint dict) +{ + Dictionary *dictionary = (Dictionary*) dict; + ((Asset*) dictionary->getAsset())->close(); + delete (Dictionary*) dict; +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMethods[] = { + {"openNative", "(Landroid/content/res/AssetManager;Ljava/lang/String;II)I", + (void*)latinime_BinaryDictionary_open}, + {"closeNative", "(I)V", (void*)latinime_BinaryDictionary_close}, + {"getSuggestionsNative", "(I[II[C[IIII)I", (void*)latinime_BinaryDictionary_getSuggestions}, + {"isValidWordNative", "(I[CI)Z", (void*)latinime_BinaryDictionary_isValidWord} +}; + +static int registerNativeMethods(JNIEnv* env, const char* className, + JNINativeMethod* gMethods, int numMethods) +{ + jclass clazz; + + clazz = env->FindClass(className); + if (clazz == NULL) { + fprintf(stderr, + "Native registration unable to find class '%s'\n", className); + return JNI_FALSE; + } + if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { + fprintf(stderr, "RegisterNatives failed for '%s'\n", className); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +static int registerNatives(JNIEnv *env) +{ + const char* const kClassPathName = "com/android/inputmethod/latin/BinaryDictionary"; + jclass clazz; + + clazz = env->FindClass("java/io/FileDescriptor"); + if (clazz == NULL) { + LOGE("Can't find %s", "java/io/FileDescriptor"); + return -1; + } + sDescriptorField = env->GetFieldID(clazz, "descriptor", "I"); + + clazz = env->FindClass("android/content/res/AssetManager"); + if (clazz == NULL) { + LOGE("Can't find %s", "java/io/FileDescriptor"); + return -1; + } + sAssetManagerNativeField = env->GetFieldID(clazz, "mObject", "I"); + + return registerNativeMethods(env, + kClassPathName, gMethods, sizeof(gMethods) / sizeof(gMethods[0])); +} + +/* + * Returns the JNI version on success, -1 on failure. + */ +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + fprintf(stderr, "ERROR: GetEnv failed\n"); + goto bail; + } + assert(env != NULL); + + if (!registerNatives(env)) { + fprintf(stderr, "ERROR: BinaryDictionary native registration failed\n"); + goto bail; + } + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + +bail: + return result; +} diff --git a/dictionary/src/basechars.h b/dictionary/src/basechars.h new file mode 100644 index 000000000..5a4406606 --- /dev/null +++ b/dictionary/src/basechars.h @@ -0,0 +1,172 @@ +/** + * Table mapping most combined Latin, Greek, and Cyrillic characters + * to their base characters. If c is in range, BASE_CHARS[c] == c + * if c is not a combined character, or the base character if it + * is combined. + */ +static unsigned short BASE_CHARS[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0020, + 0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7, + 0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf, + 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00c6, 0x0043, + 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, + 0x00d0, 0x004e, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x00d7, + 0x004f, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00de, 0x0073, // Manually changed d8 to 4f + // Manually changed df to 73 + 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063, + 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069, + 0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7, + 0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079, // Manually changed f8 to 6f + 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063, + 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064, + 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065, + 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067, + 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127, + 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, + 0x0049, 0x0131, 0x0049, 0x0069, 0x004a, 0x006a, 0x004b, 0x006b, + 0x0138, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, + 0x006c, 0x0141, 0x0142, 0x004e, 0x006e, 0x004e, 0x006e, 0x004e, + 0x006e, 0x02bc, 0x014a, 0x014b, 0x004f, 0x006f, 0x004f, 0x006f, + 0x004f, 0x006f, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072, + 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073, + 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167, + 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, + 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079, + 0x0059, 0x005a, 0x007a, 0x005a, 0x007a, 0x005a, 0x007a, 0x0073, + 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187, + 0x0188, 0x0189, 0x018a, 0x018b, 0x018c, 0x018d, 0x018e, 0x018f, + 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197, + 0x0198, 0x0199, 0x019a, 0x019b, 0x019c, 0x019d, 0x019e, 0x019f, + 0x004f, 0x006f, 0x01a2, 0x01a3, 0x01a4, 0x01a5, 0x01a6, 0x01a7, + 0x01a8, 0x01a9, 0x01aa, 0x01ab, 0x01ac, 0x01ad, 0x01ae, 0x0055, + 0x0075, 0x01b1, 0x01b2, 0x01b3, 0x01b4, 0x01b5, 0x01b6, 0x01b7, + 0x01b8, 0x01b9, 0x01ba, 0x01bb, 0x01bc, 0x01bd, 0x01be, 0x01bf, + 0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0044, 0x0044, 0x0064, 0x004c, + 0x004c, 0x006c, 0x004e, 0x004e, 0x006e, 0x0041, 0x0061, 0x0049, + 0x0069, 0x004f, 0x006f, 0x0055, 0x0075, 0x00dc, 0x00fc, 0x00dc, + 0x00fc, 0x00dc, 0x00fc, 0x00dc, 0x00fc, 0x01dd, 0x00c4, 0x00e4, + 0x0226, 0x0227, 0x00c6, 0x00e6, 0x01e4, 0x01e5, 0x0047, 0x0067, + 0x004b, 0x006b, 0x004f, 0x006f, 0x01ea, 0x01eb, 0x01b7, 0x0292, + 0x006a, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01f6, 0x01f7, + 0x004e, 0x006e, 0x00c5, 0x00e5, 0x00c6, 0x00e6, 0x00d8, 0x00f8, + 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065, + 0x0049, 0x0069, 0x0049, 0x0069, 0x004f, 0x006f, 0x004f, 0x006f, + 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075, + 0x0053, 0x0073, 0x0054, 0x0074, 0x021c, 0x021d, 0x0048, 0x0068, + 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061, + 0x0045, 0x0065, 0x00d6, 0x00f6, 0x00d5, 0x00f5, 0x004f, 0x006f, + 0x022e, 0x022f, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237, + 0x0238, 0x0239, 0x023a, 0x023b, 0x023c, 0x023d, 0x023e, 0x023f, + 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247, + 0x0248, 0x0249, 0x024a, 0x024b, 0x024c, 0x024d, 0x024e, 0x024f, + 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257, + 0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f, + 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267, + 0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f, + 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277, + 0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f, + 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287, + 0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f, + 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, + 0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f, + 0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7, + 0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af, + 0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077, + 0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf, + 0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7, + 0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf, + 0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7, + 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df, + 0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7, + 0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef, + 0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7, + 0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff, + 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, + 0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f, + 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, + 0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f, + 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, + 0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f, + 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, + 0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f, + 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347, + 0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f, + 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, + 0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f, + 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, + 0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f, + 0x0370, 0x0371, 0x0372, 0x0373, 0x02b9, 0x0375, 0x0376, 0x0377, + 0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f, + 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00a8, 0x0391, 0x00b7, + 0x0395, 0x0397, 0x0399, 0x038b, 0x039f, 0x038d, 0x03a5, 0x03a9, + 0x03ca, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, + 0x03a8, 0x03a9, 0x0399, 0x03a5, 0x03b1, 0x03b5, 0x03b7, 0x03b9, + 0x03cb, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, + 0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03cf, + 0x03b2, 0x03b8, 0x03a5, 0x03d2, 0x03d2, 0x03c6, 0x03c0, 0x03d7, + 0x03d8, 0x03d9, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df, + 0x03e0, 0x03e1, 0x03e2, 0x03e3, 0x03e4, 0x03e5, 0x03e6, 0x03e7, + 0x03e8, 0x03e9, 0x03ea, 0x03eb, 0x03ec, 0x03ed, 0x03ee, 0x03ef, + 0x03ba, 0x03c1, 0x03c2, 0x03f3, 0x0398, 0x03b5, 0x03f6, 0x03f7, + 0x03f8, 0x03a3, 0x03fa, 0x03fb, 0x03fc, 0x03fd, 0x03fe, 0x03ff, + 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406, + 0x0408, 0x0409, 0x040a, 0x040b, 0x041a, 0x0418, 0x0423, 0x040f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0418, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, + 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456, + 0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f, + 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467, + 0x0468, 0x0469, 0x046a, 0x046b, 0x046c, 0x046d, 0x046e, 0x046f, + 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475, + 0x0478, 0x0479, 0x047a, 0x047b, 0x047c, 0x047d, 0x047e, 0x047f, + 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, + 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f, + 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, + 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x049f, + 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, + 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af, + 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, + 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x04be, 0x04bf, + 0x04c0, 0x0416, 0x0436, 0x04c3, 0x04c4, 0x04c5, 0x04c6, 0x04c7, + 0x04c8, 0x04c9, 0x04ca, 0x04cb, 0x04cc, 0x04cd, 0x04ce, 0x04cf, + 0x0410, 0x0430, 0x0410, 0x0430, 0x04d4, 0x04d5, 0x0415, 0x0435, + 0x04d8, 0x04d9, 0x04d8, 0x04d9, 0x0416, 0x0436, 0x0417, 0x0437, + 0x04e0, 0x04e1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041e, 0x043e, + 0x04e8, 0x04e9, 0x04e8, 0x04e9, 0x042d, 0x044d, 0x0423, 0x0443, + 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04f6, 0x04f7, + 0x042b, 0x044b, 0x04fa, 0x04fb, 0x04fc, 0x04fd, 0x04fe, 0x04ff, +}; + +// generated with: +// cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; $base[hex($foo[0])] = hex($foo[5]);} for ($i = 0; $i < 0x500; $i += 8) { for ($j = $i; $j < $i + 8; $j++) { printf("0x%04x, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }' diff --git a/dictionary/src/dictionary.cpp b/dictionary/src/dictionary.cpp new file mode 100644 index 000000000..6aecb6374 --- /dev/null +++ b/dictionary/src/dictionary.cpp @@ -0,0 +1,277 @@ +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include +#include +#include +#include +#include + +#include + +//#define USE_ASSET_MANAGER + +#ifdef USE_ASSET_MANAGER +#include +#include +#endif + +#include "dictionary.h" +#include "basechars.h" + +#define DEBUG_DICT 0 + +namespace latinime { + +Dictionary::Dictionary(void *dict, int typedLetterMultiplier, int fullWordMultiplier) +{ + mDict = (unsigned char*) dict; + mTypedLetterMultiplier = typedLetterMultiplier; + mFullWordMultiplier = fullWordMultiplier; +} + +Dictionary::~Dictionary() +{ +} + +int Dictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies, + int maxWordLength, int maxWords, int maxAlternatives) +{ + memset(frequencies, 0, maxWords * sizeof(*frequencies)); + memset(outWords, 0, maxWords * maxWordLength * sizeof(*outWords)); + + mFrequencies = frequencies; + mOutputChars = outWords; + mInputCodes = codes; + mInputLength = codesSize; + mMaxAlternatives = maxAlternatives; + mMaxWordLength = maxWordLength; + mMaxWords = maxWords; + mWords = 0; + + getWordsRec(0, 0, mInputLength * 3, false, 1, 0); + + if (DEBUG_DICT) LOGI("Returning %d words", mWords); + return mWords; +} + +unsigned short +Dictionary::getChar(int *pos) +{ + unsigned short ch = (unsigned short) (mDict[(*pos)++] & 0xFF); + // If the code is 255, then actual 16 bit code follows (in big endian) + if (ch == 0xFF) { + ch = ((mDict[*pos] & 0xFF) << 8) | (mDict[*pos + 1] & 0xFF); + (*pos) += 2; + } + return ch; +} + +int +Dictionary::getAddress(int *pos) +{ + int address = 0; + address += (mDict[*pos] & 0x7F) << 16; + address += (mDict[*pos + 1] & 0xFF) << 8; + address += (mDict[*pos + 2] & 0xFF); + *pos += 3; + return address; +} + +int +Dictionary::wideStrLen(unsigned short *str) +{ + if (!str) return 0; + unsigned short *end = str; + while (*end) + end++; + return end - str; +} + +bool +Dictionary::addWord(unsigned short *word, int length, int frequency) +{ + word[length] = 0; + if (DEBUG_DICT) LOGI("Found word = %s, freq = %d : \n", word, frequency); + + // Find the right insertion point + int insertAt = 0; + while (insertAt < mMaxWords) { + if (frequency > mFrequencies[insertAt] + || (mFrequencies[insertAt] == frequency + && length < wideStrLen(mOutputChars + insertAt * mMaxWordLength))) { + break; + } + insertAt++; + } + if (insertAt < mMaxWords) { + memmove((char*) mFrequencies + (insertAt + 1) * sizeof(mFrequencies[0]), + (char*) mFrequencies + insertAt * sizeof(mFrequencies[0]), + (mMaxWords - insertAt - 1) * sizeof(mFrequencies[0])); + mFrequencies[insertAt] = frequency; + memmove((char*) mOutputChars + (insertAt + 1) * mMaxWordLength * sizeof(short), + (char*) mOutputChars + (insertAt ) * mMaxWordLength * sizeof(short), + (mMaxWords - insertAt - 1) * sizeof(short) * mMaxWordLength); + unsigned short *dest = mOutputChars + (insertAt ) * mMaxWordLength; + while (length--) { + *dest++ = *word++; + } + *dest = 0; // NULL terminate + // Update the word count + if (insertAt + 1 > mWords) mWords = insertAt + 1; + if (DEBUG_DICT) LOGI("Added word at %d\n", insertAt); + return true; + } + return false; +} + +unsigned short +Dictionary::toLowerCase(unsigned short c, const int depth) { + if (c < sizeof(BASE_CHARS) / sizeof(BASE_CHARS[0])) { + c = BASE_CHARS[c]; + } + if (depth == 0) { + if (c >='A' && c <= 'Z') { + c |= 32; + } else if (c > 127) { + c = u_tolower(c); + } + } + return c; +} + +bool +Dictionary::sameAsTyped(unsigned short *word, int length) +{ + if (length != mInputLength) { + return false; + } + int *inputCodes = mInputCodes; + while (length--) { + if ((unsigned int) *inputCodes != (unsigned int) *word) { + return false; + } + inputCodes += mMaxAlternatives; + word++; + } + return true; +} + +static char QUOTE = '\''; + +void +Dictionary::getWordsRec(int pos, int depth, int maxDepth, bool completion, int snr, int inputIndex) +{ + // Optimization: Prune out words that are too long compared to how much was typed. + if (depth > maxDepth) { + return; + } + int count = getCount(&pos); + int *currentChars = NULL; + if (mInputLength <= inputIndex) { + completion = true; + } else { + currentChars = mInputCodes + (inputIndex * mMaxAlternatives); + } + + for (int i = 0; i < count; i++) { + unsigned short c = getChar(&pos); + unsigned short lowerC = toLowerCase(c, depth); + bool terminal = getTerminal(&pos); + int childrenAddress = getAddress(&pos); + int freq = getFreq(&pos); + // If we are only doing completions, no need to look at the typed characters. + if (completion) { + mWord[depth] = c; + if (terminal) { + addWord(mWord, depth + 1, freq * snr); + } + if (childrenAddress != 0) { + getWordsRec(childrenAddress, depth + 1, maxDepth, + completion, snr, inputIndex); + } + } else if (c == QUOTE && currentChars[0] != QUOTE) { + // Skip the ' and continue deeper + mWord[depth] = QUOTE; + if (childrenAddress != 0) { + getWordsRec(childrenAddress, depth + 1, maxDepth, false, snr, inputIndex); + } + } else { + int j = 0; + while (currentChars[j] > 0) { + int addedWeight = j == 0 ? mTypedLetterMultiplier : 1; + if (currentChars[j] == lowerC || currentChars[j] == c) { + mWord[depth] = c; + if (mInputLength == inputIndex + 1) { + if (terminal) { + if (//INCLUDE_TYPED_WORD_IF_VALID || + !sameAsTyped(mWord, depth + 1)) { + addWord(mWord, depth + 1, + (freq * snr * addedWeight * mFullWordMultiplier)); + } + } + if (childrenAddress != 0) { + getWordsRec(childrenAddress, depth + 1, + maxDepth, true, snr * addedWeight, inputIndex + 1); + } + } else if (childrenAddress != 0) { + getWordsRec(childrenAddress, depth + 1, maxDepth, + false, snr * addedWeight, inputIndex + 1); + } + } + j++; + } + } + } +} + +bool +Dictionary::isValidWord(unsigned short *word, int length) +{ + return isValidWordRec(0, word, 0, length); +} + +bool +Dictionary::isValidWordRec(int pos, unsigned short *word, int offset, int length) { + int count = getCount(&pos); + unsigned short currentChar = (unsigned short) word[offset]; + for (int j = 0; j < count; j++) { + unsigned short c = getChar(&pos); + int terminal = getTerminal(&pos); + int childPos = getAddress(&pos); + if (c == currentChar) { + if (offset == length - 1) { + if (terminal) { + return true; + } + } else { + if (childPos != 0) { + if (isValidWordRec(childPos, word, offset + 1, length)) { + return true; + } + } + } + } + getFreq(&pos); + // There could be two instances of each alphabet - upper and lower case. So continue + // looking ... + } + return false; +} + + +} // namespace latinime diff --git a/dictionary/src/dictionary.h b/dictionary/src/dictionary.h new file mode 100644 index 000000000..8574e0736 --- /dev/null +++ b/dictionary/src/dictionary.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICTIONARY_H +#define LATINIME_DICTIONARY_H + +namespace latinime { + +class Dictionary { +public: + Dictionary(void *dict, int typedLetterMultipler, int fullWordMultiplier); + int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies, + int maxWordLength, int maxWords, int maxAlternatives); + bool isValidWord(unsigned short *word, int length); + void setAsset(void *asset) { mAsset = asset; } + void *getAsset() { return mAsset; } + ~Dictionary(); + +private: + + int getAddress(int *pos); + bool getTerminal(int *pos) { return (mDict[*pos] & 0x80) > 0; } + int getFreq(int *pos) { return mDict[(*pos)++] & 0xFF; } + int getCount(int *pos) { return mDict[(*pos)++] & 0xFF; } + unsigned short getChar(int *pos); + int wideStrLen(unsigned short *str); + + bool sameAsTyped(unsigned short *word, int length); + bool addWord(unsigned short *word, int length, int frequency); + unsigned short toLowerCase(unsigned short c, int depth); + void getWordsRec(int pos, int depth, int maxDepth, bool completion, int frequency, + int inputIndex); + bool isValidWordRec(int pos, unsigned short *word, int offset, int length); + + unsigned char *mDict; + void *mAsset; + + int *mFrequencies; + int mMaxWords; + int mMaxWordLength; + int mWords; + unsigned short *mOutputChars; + int *mInputCodes; + int mInputLength; + int mMaxAlternatives; + unsigned short mWord[128]; + + int mFullWordMultiplier; + int mTypedLetterMultiplier; +}; + +// ---------------------------------------------------------------------------- + +}; // namespace latinime + +#endif // LATINIME_DICTIONARY_H diff --git a/res/drawable-land/keyboard_suggest_strip_divider.png b/res/drawable-land/keyboard_suggest_strip_divider.png new file mode 100644 index 0000000000000000000000000000000000000000..e54c5b099431be9853118c033e6fee440e44aa61 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^OhBy00U{G$-VFv)k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XJUm?-Ln?0d^f~e|7_hKJfBb*`XD@STZt$Zdp^0}R7z7QC zJ33Z1S*_<)pAj9ID(Y6%nD%G&tkd;!!sj`ry)J!Ta$v{V2qqqeESB;(&)M-UKr!g4&nzO(G#5f2*!`V2~L{u0l!hssErY1s2t?ld9 zow{45+u7Gb|2#<8WYSG{yYrjb+4pv5%j)&IH;BR*YYeQ$XWoDc!|+OB$Nm>-S7S@U zC{FyYlLzaO@D6ZB)LDo2Zc4pFy&eVLWQGa>&WKOD&K&)^1;?LQJfhxaM1z}vcR_-1 z>odxbPT;mk>(gbF6W#{LuzY!W`TqF$_)-uAbDZ-h>UZ~gU35zSPbuZ+YPI^QSS)_n z+1dH74WNu^8sLJ*;70tlwY7C2#7l3$B@Dy28yg$1RJDeTuxFzUo&xtlR##WomA79& zzuDPYnNFu=DwUFBV`Du(XHQN}SfNnh`Fx%q92{`qQM2%Fb92)eP@@cR!DDc-xVWe| zQ#t4}KR;hvSXihMQ`ytw+UVWg-3|8k_IzMc20hATG6k3n$^huYT>}}fPfScKK?8VP zfmifkAsVz-X^scugC5YQ_4-=4^-yROPJm;0kw_%wpizYiR@m>?a@~xr^>|vZAmEz@ z2;%XE!zNIR^f7#?LMPPIHpn8i9-}wVI%}Fqp1-J>ba~!Pmr$MVLZe!oasVE0n*puI z(|QGBY4bc4^DPVZ0{f4Ch7gVnA2B&&o?NhAyya3078?*ZJa=qEJnnez;Kv|j1Tm{DQVmOm_) z%U_`3`1p7z2NR=5>y@t1ft`UX=9_7@v$L}-e3{SZ#Sk`XOzZKqUOV0AT(DHuwsv%M z^bs1f*{sN9G9!Z|hw(uV)V1DPl=p=rY)r1CmdoXS%*@PekB*K$-{0R)6pKZkPN%Dr zlarxIQ}h_)T!o5I9uEK<(s-w*r`w>{Yt2=*J#kE~5}%yFpoA9i^lc@{m6es(3ijC$ z7VP`2t*tkzT2k@{!e2>6i%EP*NLFHahTx3>x3n}2n4X?aO-)TbRYp8aCX)|@5O;co z4a4wvsZ{!=pua+>93CDP5cpp$I7ce~uh(;U`=l;(sx7b#7|~+BGkxs*O4M^(qHw5c zF_2{~7~J#;@dBn&+;+AMhY+5L1Oe`Q?&}@<9a19A^-pY07*qoM6N<$f{XA!Jpcdz literal 0 HcmV?d00001 diff --git a/res/drawable/dialog_bubble_step02.9.png b/res/drawable/dialog_bubble_step02.9.png new file mode 100755 index 0000000000000000000000000000000000000000..d77f85fe28e807d099c5bef39e8eb50c8aed60bb GIT binary patch literal 1036 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 literal 0 HcmV?d00001 diff --git a/res/drawable/ic_dialog_keyboard.png b/res/drawable/ic_dialog_keyboard.png new file mode 100644 index 0000000000000000000000000000000000000000..9a5aada8bb425f4a40d6f0f540f724ac94ffa147 GIT binary patch literal 528 zcmV+r0`L8aP)MlvC?9;k08Cd4~CT`|q zcV>uYQ51$wGiw3c?bdF!S`)zbopUEymQBsm>GTa`r0D=W6rlAEY5_vP`Fu8_D57A$ z-zTq`_~^eQ=s`0cvm@mSzmEEexL6L29`&3RtN2a)4A;$F&F z73X<{fJUP+IUEil4hX~;Q^|Q=6Q+^gE=bdq1SCmf_`XlU;c!S^CAaPCbrJkLo;yWRfc$5O_sIOKh*6k(^+dE`GRV^v%f;byZb7h!T;gFA7F_XS3M@ z`deIPvi-;*-)lucx7!sjDC7UQ%w+qK^St5;;8-Sc9Mh;ojIkTsljI*r_7fLfFrUwh zX0xef4Da`Qg9Sn0^?JP#ugZHFb{%w}$KXro8QMe3XF05C{m-Ztpk2U^00RK^^gI>O SB(i4!0000 + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_suggest_strip_scroll_left_arrow.png b/res/drawable/ic_suggest_strip_scroll_left_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..a9adef2ba5a727e0270eafdb2de1dc2a32d5a76c GIT binary patch literal 445 zcmV;u0Yd(XP)&M1Kt@J}K}1A^fsv7sfti_^fuIY5gM$r#&fU+>&Mu&!puoV-&(FZg z$;t5d?_UO>q4>ifFfgzH}(jsKZuKqGXTTp0lFGSVrr&IlO_oOU84XD nqYGpiFj^vw8Zh_`00=Mu3SEkNqBo^b00000NkvXXu0mjfuG6_P literal 0 HcmV?d00001 diff --git a/res/drawable/ic_suggest_strip_scroll_right_arrow.png b/res/drawable/ic_suggest_strip_scroll_right_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..639a28711cd019a6feeaf2f84b50381e5a834ae3 GIT binary patch literal 440 zcmV;p0Z0CcP)To!~BSFK1Q36W9GXqT1)Jbap0yvJNF$}ZdIBscK*0?aB(P(I@szS5b1W^>{1UN4Q zpePD>o`+7S1HE1kBuN^(uDckGMzucyP9GT32FS7u{eB-II)N??!_e;n(lmwret)i& zN+pAk)@tN>F&qxxZURuW+wIPYAeyG3+wDTT-G+L-{$bnp8NyyW0Vax3co_(3wOX)V zuj4`hti%zhR;w#w?;r?*=;jPATR>M(OosTKB*}~CdC}b=o**pCW@1vmHk-}sWHO2K z&$HLO#kUHAkcr9h-+kZz{B4nb0EqW74oXp!)polbOsCW6<}Tyn8U}fi7AzZCsS>Jit0RF978H@y_s&!$DqjJ(il+j)b`-Kn{Hw~-^2pe%=Glp z`Ow-kzcVMTL@VuE(c{Ip7=Aak3q3tk-c;P9p zVd(KqOp}a`v$(&S*08bigiS+X-}}9l|J1HGxGrH^v+Cu8zp~z@mO(Ft6Bw5LyL0Z~ T#dK+)OBg&|{an^LB{Ts5MqgH! literal 0 HcmV?d00001 diff --git a/res/drawable/keyboard_suggest_strip_divider.png b/res/drawable/keyboard_suggest_strip_divider.png new file mode 100644 index 0000000000000000000000000000000000000000..e54c5b099431be9853118c033e6fee440e44aa61 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^OhBy00U{G$-VFv)k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XJUm?-Ln?0d^f~e|7_hKJfBb*`XD@STZt$Zdp^0}R7z7QC zJ33Z1S*_<)pAj9ID(Y6%nD%G&tkd;!!sj`ry)J!Ta$v{V2qqqeESB;(&)M-UKr&&$;i2Zns-FOidvivZmUv zQxrwd4;!vvnOy2(2wr{i=Dhpl=JwSEIvG_EgUJ955C_DwOfNPACcw$M1R$2@SOE)< zQ}C~y;f%^d{KB}Bl9I=Zii*x#ES6ZMQt51JYWf!XKX~yYU0q!-n@pxS>Pt>ezF$*Q z^9@ZN9UVQtxVX61($cbo$E{Ed^0+P#(A2=m_V)Jo++J;adwa8?q2X=xJp&ZGeHwJ< zfage_nVI<%@@X{b9tE(H<>-8VOQ}*WO=Ic2!Kt784U=U0YkbJ2*J_RYF3-(aOror=Wh2`m(dL^;!W8zg%O?k8^~!5gROd1-0M$cqb8Q&T^bmX>}E(ZHVf%z=TFeaC^jJk~1D ziS)=MjYhLeLQYOjT3cJ&xw5jd;`R0QA15a#e}bAkBBZ9KCf3*2pG5!D>;ccBDJdzs z#>U38eSLk!czhtJ6GF(cG^F*rat7IKHp{}o!gmD)1((Q1pwLg2ruZGhPlEm9kkOJ-f0}q5u>P|yX?VxTz>4K8YJ(Aj^ojZ ziHRDuTAjpc9Co95E|*K8QmGvA@$sg@!onNuMZ4di_E*Dz$AFjS=jUhL2iz#=X|ujT zt=h*k#2rt}&COlU%gf6*8jW{aTU)=-YPA;HB{8rhNgBJ|PGd#rc2Qg%@Pd-`^diuO z9k5Z+#W?NG&Q20a&IM4!ai#!uz_j<{nTs&yd)f_*F=k|BT%!~Rh-b1mwM(k1s&4f3 z^xTB}CQt#qQBhIRGB7YO0{JpImn0ZY%&Uu9;Nxho`tqPgUYG9RJ(+o<{J zkBA>4IWm!t;>^&{(4g#@>FH^MER#OkBMT>}J+x3=U42zH0N31G&^2~+bbKl6q7CYB z)1K1#wcg&|&$_$2KZpDlK!#r{FE4NF@9+O4lC=N3+Itn(J;aEs#y^OWh>k98M2bv| z0?G;zqeR3A($uidPPa^d#EA7?q01aa#(%DK$kz_KYTh9^KR^HdgD$T8U1!^qx_*vq z&u0*SnLhMyTtBeBJ)HZHi+3UMEm3&@e^9vj3s&;?C94Yd@$dELf%L!pJLb?C^p^kw Y0Fy#=sqh&+d;kCd07*qoM6N<$f~8NZ4*&oF literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_done.png b/res/drawable/sym_keyboard_done.png new file mode 100755 index 0000000000000000000000000000000000000000..c0d6d1394b4912a2508055ec7979f4a6c2bbc4ca GIT binary patch literal 771 zcmV+e1N{7nP)DN7IhR2Ags-S5F5wW^ zLF7VUlYs~bmxKW@;fEE1W!v`C(P%Uba~|xIeKQBtyb&4>$(@UTJ4w~eU;G69+T!Ao~@{$#%@VT%Rr!4V@~rjOVPv#~yqx5#n|Ky=aUo6?Slh2@g?~w*XBcz+s<4 zD{-5F-`QWucU$<75E?v!PYoOv9V4?Ib5Fu;fa5qPdGlYOJ;`i1NmWs!R7!ks!q zib*2!rji7GC1I}KpC?L;N&o#Ip-0E2ItW~%!_=^qcR*x_`|~6|WXcHbl9P`h_+BX! z++9Aw{l`%rO7y`gB4nUJx9GPoaPKp=^F!FLadP2002ovPDHLkV1krB BVQ>Hd literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_feedback_delete.png b/res/drawable/sym_keyboard_feedback_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9083972d92e50a87efcaafe02814b36dcd7238 GIT binary patch literal 524 zcmV+n0`vWeP)tf>{p;YxrPXYgUdH2pXo2F^ErFhwGEE1ta z=>HL7jFo^%K@X@cV+2~Izs0uF_PxCa`2p|+I04>Z@mJWx&W17a*+0a_I0p#vuf#*E zwDs6ln8uJbTI?@v-wXASRDq@-7wJTlAq(##J(y8KBAv?nWo<$udB1?{hgHiSQjwvt zMSL$^_C5*;e|*625mWOCY79CL$tr{XhV-8lZ#A0vUR5U10cy(_T9vJ)Ut4i3-G}}f zA?CQ(6i?d6{{HajBUYO zxr)%S9NYQK{CdtqsxqU4GV*X86#|v!Ia-;Ur;(|u>M_ABa~~9nB{R1@aEE!?l;w!n zd(oA&{lqL-pgN%{aYdP%M^R`~7E6i~0zQ>GODKd?u8gk?p6X%&NCUXz7xl2qi*^&~L|GfB^ve-=TlpZrCjV O0000xt*`Q1UOu!1=0N!{^M__=WnhBZ>!d>NyPF8~5+O93?ldQB(lYTz`|6LR@3`3D} zhzytkGhhZhcfi~0x`N|7jEBV7)-c-3tE&1;5Due;agQ0KJ%0WEkuNEb0TXgOKm+K>U}oB(?! z+CI?SpM{!)(m=YSIhkt^n6NH)dYPHM(1#ms3^ipc;}*ke_M!%HXOsL-lbk|-6S5jj zQrd|3lnxHjNo*sUNe+2JiDDAlILahOKzNA#LIY`f8RtwI<@(aEwJ~8CQs6#Dbp&+#28Tx!vnuF?rfEFi z!?`vV;7%C}TUJAm3a%s5!Tizx~lS3GTy-i}O2b$M_Xs05(wrD8#Qi1poj507*qoM6N<$g0uY9y8r+H literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_feedback_numalt.png b/res/drawable/sym_keyboard_feedback_numalt.png new file mode 100644 index 0000000000000000000000000000000000000000..aac7376156723faa1c9595b615b13e80cd457e55 GIT binary patch literal 735 zcmV<50wDc~P))RPL!hCzc(q?SXu!OCme6p8RAh5VdVPxtBJb z+))#-0#?8ZSOF_w1+0L*N}K&RhoUG7c}<}-Q2ti8ys|M!2XGIP4vy?T1pY$M$Wz@IS3<6s&OP;ZO0S&%bs%X~L9HB!cITweR=(dXik=#C);1vwuxOE7Zo z0zrGT9Z$ivF{yty__!op`rfxLR-t90(Msck;40;!j{4rxx99A>#yjoTwD!``V`XDf zaTTL!j0Ritn6S{hpKLlM8og1C%2{+U8|cqyG0*&lEi3^m1HdX0 z?CdHV)Gz;n;oEY$y0;FNR%FGH?MVKN7O(BD7yyXV=@9bm RZ%P0F002ovPDHLkV1l%eOSAw0 literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_feedback_numpound.png b/res/drawable/sym_keyboard_feedback_numpound.png new file mode 100644 index 0000000000000000000000000000000000000000..6b6561e806ffab06d2ad8082c9e68d6c57899394 GIT binary patch literal 478 zcmV<40U`d0P)FDb znD%uYk-(<>0x~7{*EA0tQRmM_75m5t$ zx*_bVfoFR8WNO654$mco_r!p2z^CvT+Gi7WOzXoTzU}MchXSsD>suqC5xEA;u;M1& literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_feedback_numstar.png b/res/drawable/sym_keyboard_feedback_numstar.png new file mode 100644 index 0000000000000000000000000000000000000000..05f7b4fc99bbf7a7b6b76718b40e0c89a24edd83 GIT binary patch literal 464 zcmV;>0WbcEP)$Y)l{a96*0^Vpw|^MmsRNh+*s*kpA5tCI?6ZCtC~; z5q(3zPg!mRjJModace|+BgZ})1RPwDgK0(QNPrI|Ks3Df;^Y|6!B5z8ZHqtxf%_JO zTi>#S1Y)2=0gZ2~728b!G34YL)tP8kWdi{iiye~y)hRX=o>>KDxq&RA>+0mpv~hF3 zZ^05zPF7kv?7NNu;_e&iu2e25_CCoPoGCr2A?7O}q@PTXL8n+B%3b1{wSGhTOjjDj zuC@5UsGy601O^@xiJhSo$QW}HfNisa1m;%F9H7|aK*~U58TzwyKuQ^gv@odiOz|iq zCT?1yG`HNRQIcO*ZcOk-AGvj3`t=`L1*sqv;K zaTA=t5juh+)D3V0-9R^}*W@7lltQUJ&|H(30D5;wVS&01Q_s(s9=aP6;G8YqLLfN+8!L_y}(5no7KJp(;bxniYDG6C=8 z9stJd$B`00tr?lhL6#8%z`!L(6BT-p^`t1PN-4Qh|Pu)_{Kn)V)^@WeIM zKh6s}#T`#&Z=XJ*BJ>gG{IfT@RY0a-NI_gMOb= literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_feedback_search.png b/res/drawable/sym_keyboard_feedback_search.png new file mode 100755 index 0000000000000000000000000000000000000000..f4af341eb6ddfb6e3c6f4a5ccfd37fb76ea854bf GIT binary patch literal 501 zcmVOkcJN-E!CDPNWcW(Q|ai7yu&bj86zk|Mm<9m=H;vO+po&gvwXo_a#*S}3SQj| z7-A3u;D!FogaJThZYnXNxQbukCKqNs{eoVDW6gOgVgN%Fon(Aut|zl%?{q?dI9?y! z3$*lvQ^xnD&Sr?Z5IqH5^b}Y|uL#E&oo`BfAT}Yx1n7v~P-H{{;RPlGUAP&m_1z{K83xw~A rlJFiq<9RcH`u{)$r~v;S;3~iXv8b1C+iv8r00000NkvXXu0mjf9k9+1 literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_feedback_shift.png b/res/drawable/sym_keyboard_feedback_shift.png new file mode 100644 index 0000000000000000000000000000000000000000..97f4661f838aa7442e07b02885769e0c7e51adfa GIT binary patch literal 437 zcmV;m0ZRUfP))Bd`H(fKEW105`x9+yI>b?~;-nP5J1Rk6?Pq8zR!))8l$qL{(LrC2hM|*c^ZZ zumTW5cqp4Wb~eu8lFOe*HUZWFj=(Y;E|s$9VLDHV^IFL)0ALroas?)u6aXC%XSuwG zxloY%=Scv-0^$T6dgMO}QGyPm0^o#l;@W{X;2D1u0J*wy$m$e`LI-LDpn1Q6VV)GW z068ba6^N7tplq+$vQ{KU0O)X(DmS1;^o1|uIv-ji1U(Z%g9%83LA!#!nilegaW|zo zA-J!3b}y?Z+6Q!ku`g{iQj)Gqc~8Q2sS#DR9p8JJr{A*5Ms%dw?7?owBssexV63QsK?{vPFzPA-c7=t+f@KSmoMSY=d1u2;0brGvjBij0paKe z_>dF;3sParfT)rHfJ$b#^N44o04RJEgufwT0fvb1pc%pKb*Wnx0H{eA?wt|RUYEM| z0x)`A>W>+b8U}fi7AzZCsS>JiW)s#978H@y}5Caw?ToYHF18SfP12dd!n)IN#1#TClnlL zW}k9oKPTfPwZBZZq1%=VCe35xRB&iuU}R$95P&jHcyFB2yu@_d+B><=T>n z8<}d6kYJN)qAs)~@B>`BP!?`X7Z6ssV1paOg0NP=r3ncJ(y*Z+ER4n=Ll_dpfr1Q` z5QY{ycuqYJZ=2UrqO{cZBwz34-uqtp&ir1vP$-m>slw-Euw=<+$!E!D$!Cm6@q^ zQo!w=o}QOWOH0ck5}bE-c6P&_4PYcCMcgOdz>-HVC<5FZ85wbfVn}y)cORU&4k!Rf zVv&!LU*v#X@+Jj)y_ivwNnQo0Y-wqE#*#&gvmSub(9rM%&YH+m5+yIEFuFw77O~f- zn!FCJJ~lS?K_EFlKmQrlo+5BAtPc+lkHeZ3pg9KlGk~he$;sC%D=U*Qx6pT{j6Bc8 zJ6c{>S65dd`~w360~0WOM2wD(&I#-F_4SqH_DZ@o5y7gV(`Yo+27|#2bA5E)NL98E zXZebX3WE|v*mS$yUtsu(1gEE`=V81Tb`hNptAU!x>j6^NrnWZEdY3VthPH1Ihqqa&z|nCR)D= zxJ=49jwk(7%k#ltFe3Xpoz5j{YJ~Zr`2BtvSvMAWOmKSeo>ypXI3ND5l9G~hI4&$K zJP5moL=uq8<@>$8y&Y|BZBA;Wf~jb7L~~D!gv1?TB~*&kXf#%dZJJD`3+&%otyUMW z^qnMm)O3*N`H0vpFE3Zm&CSVZu89a}u-FmtiSfJD)z#(jcs#nz&CLR@*PGwo-fk~0 zE;ckbH@^Uj?*ZuzwOYO9a=G3ny=$bhgc>h{g!hoG=MIPCIjm!B*W#Srv)kPv$LyU9j}n_RVtMM28>Y- ztaXu98r1qlvQ(`2#0ei)V>gFix%rko%*Ob^#j-g~CVs>!h5dmpHM6hve7$`}^P2)YOC< z9h=Q|ZD?r7E6jz1=^AW-anuE`$B4WWv8XL7g}6QK6bGuJ5-9rV!2NZN*-J45+KhB<&VI}E0xJ0 z&xJq!yvgB70hoEuo6TlZV`C$BVL5|?gVR$}Q<$LK6tBB%TN_O{>g~|qc;tymG$*$etVm#KUQXcw8)arlFuyp aCBOg(>3@^x>F(qJ0000izwp& literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_num1.png b/res/drawable/sym_keyboard_num1.png new file mode 100644 index 0000000000000000000000000000000000000000..aaac11b0c7f4971ae611ac0fde0a6bf8b4b558e1 GIT binary patch literal 506 zcmVHUL%*QVC1VydIQa!Z(NNne?b9~#Ubul$`UAdq zFC0RWo8i2?-20M4M^eht_JO2Bat1Xs;wvu>JZmZs^8>$^X#i|O w&>f%P7OtHuBhLkoQDCAsfYz&1UQ(TRvGZ-^!nXN4cgWOs^m>Xue0X+soj9~$dQV@~p+^#}t zkclK-0|zR2AKq8LKl>kdB;Ht?dB5$q?|iRw&hP&|M`E>F?Vi?VZ}${88+jXf8+jY~ zr=~dy7ZQm?ED$LmV+ebUfPrBGEaDqIi7C@jbS>fW96$-o<=&aswZH(-2Mhvw!2Cq3 zo;1cn2iF&FHD+zp})WX`e?EP0|SHN zx#HsDQtXZ3Gn0{bU{@kHdKPd4oJ6t3mFG#AD(>g;yp;C|?nwK5fk3uCPv99|mrR$u zINFo{>b7s+zQ)_d@ndSxF9Mj>hR#;;2mLObaeDGbnZ-IvPOI6CM6}U z*tv6O8m<>GF-MLk^ML>$l!On20U>}7px`}jd@eXBC}?SBW@g&9ZQD{rdz@w)@QD1K zW5eSTK_z@FvagmF(GFd7mjj2*nQue4+s;KVn z?stzLKVHk(!#yi2D{14#jh~ATX|>vcvuDqKo1dRwLhnS{-LPT9%UiZ=$yF#6Dq$b) zsjaQ8%{X}Q;7=TYturNWVzLiFyq{BIn%6QN_f>#I0Vv`kkhxCiUslr#oX~V?$w)g|rVr z_ba&OpE8+Dw{qpmND#ls?`oZm=7n5t;{gfVscOMUNgm`VIQKtcb@_;aNq#N&zNd-@bjX3yU{z-n_MA$Bqil^%Id7 z0-X|8vjVB|dP75l+R4dD22)=n_$mUAgaf98T}cW7jR^Y6l`BNd)!W-U1VL8=;>{R$ z0lxu%0KFpe*2h6!!o*2CL6w!2*^!Zvp~B*>UAw+ScbzqFn&93D|)O zqCRorMEB6p&|Q#=Wp{G1SS%zYyWHH|ttU^OECli+A|fL3-0v=2xIot7=<4dKfUDhL zqU3VJ*r#JZH;%40XG&g>K4~Rj|Ni}%LXe{d|Eer6FDJ9RMP@gFv?c5^%JlT~WSRhJ zePUvwA4uE^4Gmoilcq@R2J-fTOPo4&s);}XTDNXp0xZT$h~!jz2)+&CkEND&9Aiv* z({IzJP3v=Va#Dqb`uh4dj4Aqdv)0X%m^=kCCDLUiO;CCgZ%9r~UUlHWfqIzxh~|+^ z&LfzeCxX!YxuT+?_Yuuw)M|Ad;)34O({mxGJU7H3EkEDNy=5law;$t$eGp(0OG-*| zgoQI_&Zwb@FWBTHT-?Zo;$j?=cSE3yCX=Q_Y2@Ddd{v(h}7*SxT>u$Q2bEIFD$9I2*;&3FMn zjD*n75nDTk`4f4kRcI!YN%uV(#=ijfse*$q*T=`lr-6(@I6x^5nqESL;VQ4u9IDH6 z=x&sim6aA06fE7lckhQ46&2-rz5cedv-2=wk!N;x_Gc`#2eTnhld?|l_ ze^0!`)<>90rADNgPw^d4$3bT-lQa1G`YzFEG_5FX)EM`0oY1zlwN(>&SlA*6qLVhS zUAy+xhXn@&*4o-yR$5w0LK{TE(+2^30$2QC^XAQOk9tngOurQ*LM;x7Q(9YuapP1D z78VwMw0rmNuPOVHsfufQ+)K{ouRB}CT~S>yhvx-z70O&0yPlc<8IX0|X3LUs+MN$~ z2|yfKfRw4nH^$HW{E<)W>Hbp~UEbcG>OpvgVS-d=WWs_=7)2>bD_XY~eYiOCwT0L9 zq8}%E#z1p(a}OXD6=OqFD7GfmybI^{g`9(($2ntQNo)AW1_{hOO7s8bZ#VHLu|-_k zPFM3{@(yfX&K&$4#yKNsMkmsY^*5rKik+=Q5wUM`xw~@Dzw~_yUw!Z}!0KlHd UCl*83r~m)}07*qoM6N<$f~Wdp>;M1& literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_num3.png b/res/drawable/sym_keyboard_num3.png new file mode 100644 index 0000000000000000000000000000000000000000..6f54c850f5aeb3b10c0c8c7fc2ff4f7abed361dd GIT binary patch literal 1676 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 XdL<4CY~@b$00000NkvXXu0mjfa?Y(` literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_num6.png b/res/drawable/sym_keyboard_num6.png new file mode 100644 index 0000000000000000000000000000000000000000..ea88ceb94ea9b162632fb91bbba0d806ffaf0572 GIT binary patch literal 1952 zcmV;R2VeM!P)y)8>gLQ%6WQFv5wZl#O+c9f#&FG%n#u&B zLBSEZgNNJU(C?w=+dh$V4z*e_&bK{$-}m0W_xD_W&+{InGcz-irFzLEOZc*aw}Q8V zw}M|XmxBICrP8_W)F6i_Au1LcOL=h;GRAiUw@DUb%2b%}1{;VIWEn)u&uRuc2>Bh- z3mFx_FUo6<#R#|^#0L_yZ{NQBn>TMZ_4oG=%!sbs+}w2Zdx(`s>nt{zYk4JGd*#)u zSM%cH;-cqMm`0<~GH|IWrOp#d|JEgCqeu&3H` zzk$E;jPKLt_NbU+b!|dILd2#`o8qYfAjdN@GD=RLK3#*?KFA1USQPI@3w)x1yv&;; zlGHJ0&^$+o8}rE&uWw*54!rqZ{+_@=!a91kvR7m4)NWyXV1sfzI*rXPw*Z{Yj57X zIRjqWDXc+)aMiGF_Pfm;KYcCh}v7XZjq&B z!^6X*Ov%vD5CQV=^YdH2W5#i)=jirlmsVIPplqOF>Bz{) zkd2LvDlIK70d1}XG%_+W(9_e?7n^*-FH^OF#aQ4X!E2w6B82@4ifBvLgFQK zy{}X%)g>h*MTLchKNJ@imz_CtrYtWn?6h;A?lxCf zS6}9i1%a0v3x7;L)Q;%_~-{2xO(X zBWpxcn`kV#rV-YhLYka8dGcf0SJw2TlFJ3HnaB$EZIB?)y7)H&+`j4GQWFq%8fYhE#3jke9 zMk7INHHrI}8*@ir)ti_!^4fiQ=TTv8$onI;)$0pUw+67 z8_kg21Y427JgKUxstXSfk8pH!bVfd=!l?(;qUZ7S{Rv8`Jcr_U7^&_y&KK9BqodZ{k(&0mhl5=oCLqo&YQBhH0^G%=(uxr<@FOUVkr*eQ5@WSB%YpOT~okhw} zaQP}>mo)Hq6{2E9&fz%9Yb$tPTFxv3tdtkZ+t$|BE~Lm-E^VK32ADGWTZ5=W${TwH z5?@|kUWtNYOnj6@YHA0|e}?|mycva=@>b3{)`7*2Q==1$a3HZ~vJ&3ySX81zjxCd# z3$JCvN~5{U`3U|hmBCy`*o#k{rpjBf<;9hrmh-sV{{8!-P<6@S=ADRhV&^VCIE;!; zX!BYAe}AN%3tmuwLSz9Umrt@A>X}5ItLy(m@PG9O8?)f&YE&j7MWZQ#Ta3TqNv;2@ mvx2vRw}Q8VUjo-30t^5}5C3SXOcKoi0000k;LBs literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_num7.png b/res/drawable/sym_keyboard_num7.png new file mode 100644 index 0000000000000000000000000000000000000000..4d75583afd38290276d1bc121aef4fd0c1331095 GIT binary patch literal 1997 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 D@Kq#o7A|PuZ42mc& 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 TO`sLR00000NkvXXu0mjf*Ks`` literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_numpound.png b/res/drawable/sym_keyboard_numpound.png new file mode 100644 index 0000000000000000000000000000000000000000..b2419d9abb1a6b27d54d58638ee7dec43c3cba92 GIT binary patch literal 963 zcmV;!13dhRP)cZ6Qjb zR?$R^VX%>b#JH4*F~&GaOdME@CJqcHt_=qKEKnPm%NZbqTx1qv^V*3bI;?a-@WI2=ew_ZS(bIRIvR8}JnH1@Y6cpE!Qi0T zY;H=Y(>cz9hNDKFYP!YYaGaz?I2=xJty_9}dM@yWq*x1fwNAc)2{#f!pdIM+dcCLU z;o{=r7A7a1v?NI$ip|Zv$M0*61+sT zySuxiP$(3qrlvNrJ`Vh5@?wRL+Q?yphn0jZnM}Tx|8bPdWg>ruvp~paB#1nD+J&8^ zrKPutL}CM{?Qpx@lF#QmAB)9410;C5y)nDpo->(DY5HA0eVrChp4wF_KsA0DxIZ~L z`HD_PEaq=~Ts6#4Xp4ZR5K2zzT(xj8z0H*hYW&j%rF1*g;b1nX}kcF=^!E|*KP z*=zx<-vgh3pQ;UzwPtUrf7?Q#&>axFl+9*aA!25$)!InpXJ%%uD?doYId%=tufPXn z1A)NP>FMbh9+y}9 zDzSS23<5s(1Wk&~)tcau(B*+2yb*1R*ext9L@>ok?0S29Pf!e={tKoL0L@!m>t&%S zFSyky7MH~C#@gCi2Bl3Kauc`+T#d)$A31i5-JYRZpOGN?_CzhuFf=sO4k43pWlgeW zt$22{Id`#G%yXdkFt@oQGSz1dOS-gZ=PZ%X^6y%x#q*-MysR^Hpms-QF+lwMzcbL|HQqPb*)3 l*1xoZqNOYK1l^=7e$Tq*@? zsYGqGBu#bGUEc?9A&mv0B!S)Rz{kFM@6E8^%$u3Fvx2Vcj#@3EqlSx3-X?F8x5?K` zk;>hnAPE1cc#uVwwIVo*^mytewgaWy$FfwV0d638nBXoUNBG0`I<^D*YzI^;ggwZ$ zPEJlfOKJkm(WN! zM+5W7u=n@(Uqu^kTiJK5FnI?Pyf{BU{~F}h*492zJx1ta+<*=?_)z@!-rmlDdB>LI)ds;LB|+L0;rAj)1g5ls*rbk0k>nSnqn%OKz; zoQy`Jr)g)8=kxhJ_`BV1*TBHQi{<5I>;+#4c|PW{gdc?OEEmfDt`#6(PmdFGb946< zMHx|5Rn|1^Kp1y+cDA{wK;EOWv9a;>z&a5{O_pVKb#--iczF0ZsnUesW=L`nqE5fx z-w_A|&iv0AgV5XC+kKdAYip~6a{<8==`E+GyE8L0FDblJ*=*KZC=?`uoXh2!Y4*ga zsi~VjpRWbZnM~$qB9VAS5up>&z9~-kA3We_SEGv+WXn+rV zxs4^rBV)UiPNzSEARU2ks2=4!0BgDrI@sXDSzePB$m57=8X6kvTVG#)zq7ORjYAtB zH1)i;yCadv2yCfT>TN6*i}Ht!CBEuhypBT{1pc0HEV|u++{L!D>|0-rnXg2KSE_Py zdTZq9D&OMl7j1}!U1z&mWUtsYRyD|%E(84twu+Fi_U)~HM4LHoZ_V`M)Bb5;lefv& cR{1Ny0B8Cmg;M2e6951J07*qoM6N<$f<&#yaR2}S literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_return.png b/res/drawable/sym_keyboard_return.png new file mode 100644 index 0000000000000000000000000000000000000000..17f2574398163a10896ea6b0b61dc632d18681b2 GIT binary patch literal 866 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 literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_shift_locked.png b/res/drawable/sym_keyboard_shift_locked.png new file mode 100755 index 0000000000000000000000000000000000000000..ccaf05d3bc46ff67835b401679366bf5137c00dd GIT binary patch literal 799 zcmV+)1K|9LP)#2tjDoMsV43utniY6gO?!1%|6O3Q3DlX;)iUZPJWZ{Y;sE z@AaMG2#Lhrd>j5cC?UI1O}++R1P`j! z>N~xd9wU)RJVk#DJZl!GP6(2C#k3v!xm>QSnSewklgY;zw+Egv5;d=&iI^?$W+sz) zuGyf|>2$;4@I#Eh2A<{<>6joFyFZ5Qcrw|VX+%XLkq5B50{Zo*dLBV8cKaS}!!$zl zprD8CUGOre327cdw4zhwc5*+hSQ7w?y3L2-I30)zBI8TZX!O3Qmv}^UMiJ(HXIJu{_SkH+wa+{>Z}us z3ZiXur_<>Jqt1H0KB`nIZ$xS$Q9)$49twqaj5=Gb*0xpY@jog?g>#f427|#W3z8Ql zNaGV|JgF?Jx`KZpf(D!p4B_>9snGwu$e^Y)&@GipMW4^N+wb>}Pnx1AV-nMBHeYcf z9L;=MY%;Z3mxt?{P0rqvf_}uFAu-=MOOI#yE)x|babUZ-m36VDvapd=90>_pe%mhv dO@Y4x3;^|JxT<%Hj&}e6002ovPDHLkV1kewZa@G4 literal 0 HcmV?d00001 diff --git a/res/drawable/sym_keyboard_space.png b/res/drawable/sym_keyboard_space.png new file mode 100644 index 0000000000000000000000000000000000000000..4e6273b8985050d74bd87de3b4d3d462b3967da5 GIT binary patch literal 424 zcmeAS@N?(olHy`uVBq!ia0vp^DnP8n!3HGFBdc41lw^r(L`iUdT1k0gQ7VIDN`6wR zf@f}GdTLN=VoGJ<$y6H#21YMW7srr_TW@AK`n4E{v@M^yXj)2=n=j8f$vsWp-{-a3 zvOh?fcd$3-hD4-<;f;%rUO!oH*iyJ+!;GW9C)*!T{`vFPr&$dzcI6d%OzLJ9W;PT+ zLB7FP^f`oH9e*N`>ikckev-3&@V_&!tLEEY>}v@Ar2124tN175pKs?oEdJa!?M=rX zovugo9>~wRyL0u!xD`#G%Fa$$_qysScW7wr<;32LGaMAQ`(&r&wC41t_c8_L-k$fv z>9R?B$qvQ%3FeIsU2~L@wNqAabnpxF%KYm(?|FITw7Y&U=c#zsv32BFuR8r_Q^s29 zc#q3pvd&#|+a{ScK~cf|pP&By;K*tH$0SVEePoVHPk%el(8PPu|L@J)6g$Q9TEk-h z-LhYH>g*})sC~x{ytPwkEDK@iOuKVY*DSpG*^gKGJx%}l-(K9#z|62}eM`u;D!*yK Ppl9%O^>bP0l+XkKAM?9m literal 0 HcmV?d00001 diff --git a/res/layout/bubble_text.xml b/res/layout/bubble_text.xml new file mode 100644 index 000000000..af8abce47 --- /dev/null +++ b/res/layout/bubble_text.xml @@ -0,0 +1,27 @@ + + + + diff --git a/res/layout/candidate_preview.xml b/res/layout/candidate_preview.xml new file mode 100755 index 000000000..fe2002d46 --- /dev/null +++ b/res/layout/candidate_preview.xml @@ -0,0 +1,29 @@ + + + + diff --git a/res/layout/candidates.xml b/res/layout/candidates.xml new file mode 100755 index 000000000..edd779a81 --- /dev/null +++ b/res/layout/candidates.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/input.xml b/res/layout/input.xml new file mode 100755 index 000000000..c4bcc91c3 --- /dev/null +++ b/res/layout/input.xml @@ -0,0 +1,27 @@ + + + + diff --git a/res/raw/main.dict b/res/raw/main.dict new file mode 100755 index 0000000000000000000000000000000000000000..2b473be7567ff03beee2be146b0d0f846f1fd98a GIT binary patch literal 42 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 + + + + + + + "Při stisku klávesy vibrovat" + "Zvuk při stisku klávesy" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Automatická interpunkce" + + + + + + + + + + + + + + + "%s : Uloženo" + "áàâãäåæ" + "éěèêë" + "íìîï" + "óòôõöœø" + "ůúùûü" + "š§ß" + "ňñ" + "čç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml new file mode 100644 index 000000000..4c9920865 --- /dev/null +++ b/res/values-de/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "Vibrieren auf Tastendruck" + "Sound bei Tastendruck" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Autom. Zeichensetzung" + + + + + + + + + + + + + + + "%s : Gespeichert" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-en/bools.xml b/res/values-en/bools.xml new file mode 100644 index 000000000..897f4b3db --- /dev/null +++ b/res/values-en/bools.xml @@ -0,0 +1,22 @@ + + + + true + diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml new file mode 100644 index 000000000..08815ee7c --- /dev/null +++ b/res/values-es/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "Vibrar al pulsar tecla" + "Sonido al pulsar una tecla" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Puntuación automática" + + + + + + + + + + + + + + + "%s : Guardada" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-fr/donottranslate.xml b/res/values-fr/donottranslate.xml new file mode 100644 index 000000000..527f15e94 --- /dev/null +++ b/res/values-fr/donottranslate.xml @@ -0,0 +1,25 @@ + + + + + .\u0020,;:!?\'\n()[]*&@{}/<>_+=|\u0022 + + ., + diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml new file mode 100644 index 000000000..767004d55 --- /dev/null +++ b/res/values-fr/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "Vibrer à chaque touche enfoncée" + "Son à chaque touche enfoncée" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Ponctuation automatique" + + + + + + + + + + + + + + + "%s : enregistré" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-it/donottranslate.xml b/res/values-it/donottranslate.xml new file mode 100644 index 000000000..5cb72adf5 --- /dev/null +++ b/res/values-it/donottranslate.xml @@ -0,0 +1,23 @@ + + + + + .\u0020,;:!?\'\n()[]*&@{}/<>_+=|\u0022 + diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml new file mode 100644 index 000000000..9fd770ac1 --- /dev/null +++ b/res/values-it/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "Vibra quando premi un tasto" + "Suona quando premi un tasto" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Punteggiatura automatica" + + + + + + + + + + + + + + + "%s : parola salvata" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml new file mode 100644 index 000000000..8a6c76f96 --- /dev/null +++ b/res/values-ja/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "キーのバイブレーション" + "キーを押したときの音" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "句読点を自動入力" + + + + + + + + + + + + + + + "%s:保存しました" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml new file mode 100644 index 000000000..96d92952a --- /dev/null +++ b/res/values-ko/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "키를 누를 때 진동 발생" + "버튼을 누를 때 소리 발생" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "자동 구두점 입력" + + + + + + + + + + + + + + + "%s : 저장됨" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml new file mode 100644 index 000000000..c5c828ef6 --- /dev/null +++ b/res/values-land/dimens.xml @@ -0,0 +1,23 @@ + + + + + 47dip + \ No newline at end of file diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml new file mode 100644 index 000000000..a01031852 --- /dev/null +++ b/res/values-nb/strings.xml @@ -0,0 +1,92 @@ + + + + + + + + "Vibrer ved tastetrykk" + "Lyd ved tastetrykk" + + + + + + + + + + + "Autokorriger forrige ord ved mellomrom eller linjeskift" + + + + + + + + + + + + + "Stor forbokstav" + "Start automatisk setninger med stor bokstav" + "Automatisk punktum" + + + + + + + + + + + + + + + "%s: Lagret" + "åæáàâãä" + "éèêë" + "íìîï" + "ôóòöõœø" + "üùúû" + "ߧ" + "ñ" + "ç" + "ýÿ" + + + + + + + + + "Trykk lenge på ordet lengst til venstre for å legge det til i ordlisten" + "Gå" + + + + + "?123" + "123" + "ABC" + "ALT" + diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml new file mode 100644 index 000000000..b172defbd --- /dev/null +++ b/res/values-nl/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "Trillen bij druk op een toets" + "Geluid bij druk op een toets" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Automatische interpunctie" + + + + + + + + + + + + + + + "%s : Opgeslagen" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml new file mode 100644 index 000000000..eb1a12869 --- /dev/null +++ b/res/values-pl/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "Wibruj przy naciśnięciu klawisza" + "Dźwięk przy naciśnięciu klawisza" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Automatyczna interpunkcja" + + + + + + + + + + + + + + + "%s : Zapisano" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml new file mode 100644 index 000000000..7ca302b74 --- /dev/null +++ b/res/values-ru/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "Вибрация при нажатии клавиш" + "Звук при нажатии клавиш" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "Автоматическая пунктуация" + + + + + + + + + + + + + + + "%s : сохранено" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..3525a487b --- /dev/null +++ b/res/values-zh-rCN/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "按键时振动" + "按键时发出声音" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "自动加标点" + + + + + + + + + + + + + + + "%s:已保存" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..f880fd7e9 --- /dev/null +++ b/res/values-zh-rTW/strings.xml @@ -0,0 +1,101 @@ + + + + + + + + "按下按鍵時震動" + "按下按鍵時播放音效" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "自動標點" + + + + + + + + + + + + + + + "%s:已儲存" + "àáâãäåæ" + "èéêë" + "ìíîï" + "òóôõöœø" + "ùúûü" + "§ß" + "ñ" + "ç" + "ýÿ" + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/colors.xml b/res/values/colors.xml new file mode 100644 index 000000000..c90d9f6af --- /dev/null +++ b/res/values/colors.xml @@ -0,0 +1,24 @@ + + + + #FF000000 + #FFE35900 + #ff808080 + \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml new file mode 100644 index 000000000..d757f096d --- /dev/null +++ b/res/values/dimens.xml @@ -0,0 +1,24 @@ + + + + + 50dip + 22dip + \ No newline at end of file diff --git a/res/values/donottranslate.xml b/res/values/donottranslate.xml new file mode 100644 index 000000000..8a879bdd5 --- /dev/null +++ b/res/values/donottranslate.xml @@ -0,0 +1,25 @@ + + + + + .\u0020,;:!?\n()[]*&@{}/<>_+=|\u0022 + + .,;:!? + diff --git a/res/values/durations.xml b/res/values/durations.xml new file mode 100644 index 000000000..92af68e39 --- /dev/null +++ b/res/values/durations.xml @@ -0,0 +1,25 @@ + + + + + + 40 + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 000000000..41809c15b --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,142 @@ + + + + + Android keyboard + + Android keyboard settings + + + Vibrate on keypress + + Sound on keypress + + + Correct typing errors + + + Enable input error correction + + + Landscape input errors + + + Enable input error correction + + + Word suggestions + + + Automatically correct the previous word + + + Word suggestions + + Word suggestion settings + + Enable auto completion while typing + + + Auto completion + + + Increase text field size + + Hide word suggestions in landscape view + + + Auto-capitalization + + Capitalize the start of a sentence + + Auto-punctuate + + + + + + None + Basic + Advanced + + + + 0 + + 1 + + 2 + + + @string/prediction_none + @string/prediction_basic + @string/prediction_full + + + + %s : Saved + + àáâãäåæ + + èéêë + + ìíîï + + òóôõöœø + + ùúûü + + §ß + + ñ + + ç + + ýÿ + + + + + Hold a key down to see accents (ø, ö, etc.) + + Press the back key \u21B6 to close the keyboard at any point + + Access numbers and symbols + + Press and hold the left-most word to add it to the dictionary + + + + Go + + Next + + Done + + Send + + \?123 + + 123 + + ABC + + ALT + diff --git a/res/xml-de/kbd_qwerty.xml b/res/xml-de/kbd_qwerty.xml new file mode 100755 index 000000000..763492db5 --- /dev/null +++ b/res/xml-de/kbd_qwerty.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml-fr/kbd_qwerty.xml b/res/xml-fr/kbd_qwerty.xml new file mode 100644 index 000000000..573f08a3b --- /dev/null +++ b/res/xml-fr/kbd_qwerty.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/azerty.xml b/res/xml/azerty.xml new file mode 100644 index 000000000..614aa4936 --- /dev/null +++ b/res/xml/azerty.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/kbd_alpha.xml b/res/xml/kbd_alpha.xml new file mode 100644 index 000000000..4e8af3399 --- /dev/null +++ b/res/xml/kbd_alpha.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/kbd_phone.xml b/res/xml/kbd_phone.xml new file mode 100755 index 000000000..880d9613a --- /dev/null +++ b/res/xml/kbd_phone.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/kbd_phone_symbols.xml b/res/xml/kbd_phone_symbols.xml new file mode 100755 index 000000000..9ce7a1a1f --- /dev/null +++ b/res/xml/kbd_phone_symbols.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/kbd_popup_template.xml b/res/xml/kbd_popup_template.xml new file mode 100644 index 000000000..aca46930f --- /dev/null +++ b/res/xml/kbd_popup_template.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/res/xml/kbd_qwerty.xml b/res/xml/kbd_qwerty.xml new file mode 100755 index 000000000..34e991239 --- /dev/null +++ b/res/xml/kbd_qwerty.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/kbd_symbols.xml b/res/xml/kbd_symbols.xml new file mode 100755 index 000000000..2a150397b --- /dev/null +++ b/res/xml/kbd_symbols.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/kbd_symbols_shift.xml b/res/xml/kbd_symbols_shift.xml new file mode 100755 index 000000000..6a472a412 --- /dev/null +++ b/res/xml/kbd_symbols_shift.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/method.xml b/res/xml/method.xml new file mode 100644 index 000000000..e5654e96d --- /dev/null +++ b/res/xml/method.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/res/xml/popup_domains.xml b/res/xml/popup_domains.xml new file mode 100644 index 000000000..5c86386d5 --- /dev/null +++ b/res/xml/popup_domains.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/res/xml/popup_punctuation.xml b/res/xml/popup_punctuation.xml new file mode 100644 index 000000000..9d4575db6 --- /dev/null +++ b/res/xml/popup_punctuation.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/res/xml/popup_smileys.xml b/res/xml/popup_smileys.xml new file mode 100644 index 000000000..5663fefc8 --- /dev/null +++ b/res/xml/popup_smileys.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/prefs.xml b/res/xml/prefs.xml new file mode 100644 index 000000000..1721384f1 --- /dev/null +++ b/res/xml/prefs.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/inputmethod/latin/BinaryDictionary.java b/src/com/android/inputmethod/latin/BinaryDictionary.java new file mode 100644 index 000000000..bb4f1ba46 --- /dev/null +++ b/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import java.util.Arrays; + +import android.content.Context; +import android.content.res.AssetManager; +import android.util.Log; + +/** + * Implements a static, compacted, binary dictionary of standard words. + */ +public class BinaryDictionary extends Dictionary { + + public static final int MAX_WORD_LENGTH = 48; + private static final int MAX_ALTERNATIVES = 16; + private static final int MAX_WORDS = 16; + + private static final int TYPED_LETTER_MULTIPLIER = 2; + + private int mNativeDict; + private int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES]; + private WordCallback mWordCallback; + private char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; + private int[] mFrequencies = new int[MAX_WORDS]; + + static { + try { + System.loadLibrary("jni_latinime"); + } catch (UnsatisfiedLinkError ule) { + Log.e("BinaryDictionary", "Could not load native library jni_latinime"); + } + } + + /** + * Create a dictionary from a raw resource file + * @param context application context for reading resources + * @param resId the resource containing the raw binary dictionary + */ + public BinaryDictionary(Context context, int resId) { + if (resId != 0) { + loadDictionary(context, resId); + } + } + + private native int openNative(AssetManager am, String resourcePath, int typedLetterMultiplier, + int fullWordMultiplier); + private native void closeNative(int dict); + private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); + private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize, + char[] outputChars, int[] frequencies, + int maxWordLength, int maxWords, int maxAlternatives); + private native void setParamsNative(int typedLetterMultiplier, + int fullWordMultiplier); + + private final void loadDictionary(Context context, int resId) { + AssetManager am = context.getResources().getAssets(); + String assetName = context.getResources().getString(resId); + mNativeDict = openNative(am, assetName, TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER); + } + + @Override + public void getWords(final WordComposer codes, final WordCallback callback) { + mWordCallback = callback; + final int codesSize = codes.size(); + // Wont deal with really long words. + if (codesSize > MAX_WORD_LENGTH - 1) return; + + Arrays.fill(mInputCodes, -1); + for (int i = 0; i < codesSize; i++) { + int[] alternatives = codes.getCodesAt(i); + System.arraycopy(alternatives, 0, mInputCodes, i * MAX_ALTERNATIVES, + Math.min(alternatives.length, MAX_ALTERNATIVES)); + } + Arrays.fill(mOutputChars, (char) 0); + + int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars, mFrequencies, + MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES); + + for (int j = 0; j < count; j++) { + if (mFrequencies[j] < 1) break; + int start = j * MAX_WORD_LENGTH; + int len = 0; + while (mOutputChars[start + len] != 0) { + len++; + } + if (len > 0) { + callback.addWord(mOutputChars, start, len, mFrequencies[j]); + } + } + } + + @Override + public boolean isValidWord(CharSequence word) { + if (word == null) return false; + char[] chars = word.toString().toLowerCase().toCharArray(); + return isValidWordNative(mNativeDict, chars, chars.length); + } + + public synchronized void close() { + if (mNativeDict != 0) { + closeNative(mNativeDict); + mNativeDict = 0; + } + } + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } +} diff --git a/src/com/android/inputmethod/latin/CandidateView.java b/src/com/android/inputmethod/latin/CandidateView.java new file mode 100755 index 000000000..08c68dc12 --- /dev/null +++ b/src/com/android/inputmethod/latin/CandidateView.java @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.PopupWindow; +import android.widget.TextView; + +public class CandidateView extends View { + + private static final int OUT_OF_BOUNDS = -1; + private static final List EMPTY_LIST = new ArrayList(); + + private LatinIME mService; + private List mSuggestions = EMPTY_LIST; + private boolean mShowingCompletions; + private CharSequence mSelectedString; + private int mSelectedIndex; + private int mTouchX = OUT_OF_BOUNDS; + private Drawable mSelectionHighlight; + private boolean mTypedWordValid; + + private boolean mHaveMinimalSuggestion; + + private Rect mBgPadding; + + private TextView mPreviewText; + private PopupWindow mPreviewPopup; + private int mCurrentWordIndex; + private Drawable mDivider; + + private static final int MAX_SUGGESTIONS = 32; + private static final int SCROLL_PIXELS = 20; + + private static final int MSG_REMOVE_PREVIEW = 1; + private static final int MSG_REMOVE_THROUGH_PREVIEW = 2; + + private int[] mWordWidth = new int[MAX_SUGGESTIONS]; + private int[] mWordX = new int[MAX_SUGGESTIONS]; + private int mPopupPreviewX; + private int mPopupPreviewY; + + private static final int X_GAP = 10; + + private int mColorNormal; + private int mColorRecommended; + private int mColorOther; + private Paint mPaint; + private int mDescent; + private boolean mScrolled; + private int mTargetScrollX; + + private int mTotalWidth; + + private GestureDetector mGestureDetector; + + Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REMOVE_PREVIEW: + mPreviewText.setVisibility(GONE); + break; + case MSG_REMOVE_THROUGH_PREVIEW: + mPreviewText.setVisibility(GONE); + if (mTouchX != OUT_OF_BOUNDS) { + removeHighlight(); + } + break; + } + + } + }; + + /** + * Construct a CandidateView for showing suggested words for completion. + * @param context + * @param attrs + */ + public CandidateView(Context context, AttributeSet attrs) { + super(context, attrs); + mSelectionHighlight = context.getResources().getDrawable( + com.android.internal.R.drawable.list_selector_background_pressed); + + LayoutInflater inflate = + (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mPreviewPopup = new PopupWindow(context); + mPreviewText = (TextView) inflate.inflate(R.layout.candidate_preview, null); + mPreviewPopup.setWindowLayoutMode(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + mPreviewPopup.setContentView(mPreviewText); + mPreviewPopup.setBackgroundDrawable(null); + mColorNormal = context.getResources().getColor(R.color.candidate_normal); + mColorRecommended = context.getResources().getColor(R.color.candidate_recommended); + mColorOther = context.getResources().getColor(R.color.candidate_other); + mDivider = context.getResources().getDrawable(R.drawable.keyboard_suggest_strip_divider); + + mPaint = new Paint(); + mPaint.setColor(mColorNormal); + mPaint.setAntiAlias(true); + mPaint.setTextSize(mPreviewText.getTextSize()); + mPaint.setStrokeWidth(0); + mDescent = (int) mPaint.descent(); + + mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { + @Override + public void onLongPress(MotionEvent me) { + if (mSuggestions.size() > 0) { + if (me.getX() + mScrollX < mWordWidth[0] && mScrollX < 10) { + longPressFirstWord(); + } + } + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + mScrolled = true; + mScrollX += distanceX; + if (mScrollX < 0) { + mScrollX = 0; + } + if (mScrollX + getWidth() > mTotalWidth) { + mScrollX -= distanceX; + } + mTargetScrollX = mScrollX; + showPreview(OUT_OF_BOUNDS, null); + invalidate(); + return true; + } + }); + setHorizontalFadingEdgeEnabled(true); + setWillNotDraw(false); + setHorizontalScrollBarEnabled(false); + setVerticalScrollBarEnabled(false); + mScrollX = 0; + } + + /** + * A connection back to the service to communicate with the text field + * @param listener + */ + public void setService(LatinIME listener) { + mService = listener; + } + + @Override + public int computeHorizontalScrollRange() { + return mTotalWidth; + } + + /** + * If the canvas is null, then only touch calculations are performed to pick the target + * candidate. + */ + @Override + protected void onDraw(Canvas canvas) { + if (canvas != null) { + super.onDraw(canvas); + } + mTotalWidth = 0; + if (mSuggestions == null) return; + + final int height = getHeight(); + if (mBgPadding == null) { + mBgPadding = new Rect(0, 0, 0, 0); + if (getBackground() != null) { + getBackground().getPadding(mBgPadding); + } + mDivider.setBounds(0, mBgPadding.top, mDivider.getIntrinsicWidth(), + mDivider.getIntrinsicHeight()); + } + int x = 0; + final int count = mSuggestions.size(); + final int width = getWidth(); + final Rect bgPadding = mBgPadding; + final Paint paint = mPaint; + final int touchX = mTouchX; + final int scrollX = mScrollX; + final boolean scrolled = mScrolled; + final boolean typedWordValid = mTypedWordValid; + final int y = (int) (height + mPaint.getTextSize() - mDescent) / 2; + + for (int i = 0; i < count; i++) { + CharSequence suggestion = mSuggestions.get(i); + if (suggestion == null) continue; + paint.setColor(mColorNormal); + if (mHaveMinimalSuggestion + && ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid))) { + paint.setTypeface(Typeface.DEFAULT_BOLD); + paint.setColor(mColorRecommended); + } else if (i != 0) { + paint.setColor(mColorOther); + } + final int wordWidth; + if (mWordWidth[i] != 0) { + wordWidth = mWordWidth[i]; + } else { + float textWidth = paint.measureText(suggestion, 0, suggestion.length()); + wordWidth = (int) textWidth + X_GAP * 2; + mWordWidth[i] = wordWidth; + } + + mWordX[i] = x; + + if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled) { + if (canvas != null) { + canvas.translate(x, 0); + mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height); + mSelectionHighlight.draw(canvas); + canvas.translate(-x, 0); + showPreview(i, null); + } + mSelectedString = suggestion; + mSelectedIndex = i; + } + + if (canvas != null) { + canvas.drawText(suggestion, 0, suggestion.length(), x + X_GAP, y, paint); + paint.setColor(mColorOther); + canvas.translate(x + wordWidth, 0); + mDivider.draw(canvas); + canvas.translate(-x - wordWidth, 0); + } + paint.setTypeface(Typeface.DEFAULT); + x += wordWidth; + } + mTotalWidth = x; + if (mTargetScrollX != mScrollX) { + scrollToTarget(); + } + } + + private void scrollToTarget() { + if (mTargetScrollX > mScrollX) { + mScrollX += SCROLL_PIXELS; + if (mScrollX >= mTargetScrollX) { + mScrollX = mTargetScrollX; + requestLayout(); + } + } else { + mScrollX -= SCROLL_PIXELS; + if (mScrollX <= mTargetScrollX) { + mScrollX = mTargetScrollX; + requestLayout(); + } + } + invalidate(); + } + + public void setSuggestions(List suggestions, boolean completions, + boolean typedWordValid, boolean haveMinimalSuggestion) { + clear(); + if (suggestions != null) { + mSuggestions = new ArrayList(suggestions); + } + mShowingCompletions = completions; + mTypedWordValid = typedWordValid; + mScrollX = 0; + mTargetScrollX = 0; + mHaveMinimalSuggestion = haveMinimalSuggestion; + // Compute the total width + onDraw(null); + invalidate(); + requestLayout(); + } + + public void scrollPrev() { + int i = 0; + final int count = mSuggestions.size(); + int firstItem = 0; // Actually just before the first item, if at the boundary + while (i < count) { + if (mWordX[i] < mScrollX + && mWordX[i] + mWordWidth[i] >= mScrollX - 1) { + firstItem = i; + break; + } + i++; + } + int leftEdge = mWordX[firstItem] + mWordWidth[firstItem] - getWidth(); + if (leftEdge < 0) leftEdge = 0; + updateScrollPosition(leftEdge); + } + + public void scrollNext() { + int i = 0; + int targetX = mScrollX; + final int count = mSuggestions.size(); + int rightEdge = mScrollX + getWidth(); + while (i < count) { + if (mWordX[i] <= rightEdge && + mWordX[i] + mWordWidth[i] >= rightEdge) { + targetX = Math.min(mWordX[i], mTotalWidth - getWidth()); + break; + } + i++; + } + updateScrollPosition(targetX); + } + + private void updateScrollPosition(int targetX) { + if (targetX != mScrollX) { + // TODO: Animate + mTargetScrollX = targetX; + requestLayout(); + invalidate(); + mScrolled = true; + } + } + + public void clear() { + mSuggestions = EMPTY_LIST; + mTouchX = OUT_OF_BOUNDS; + mSelectedString = null; + mSelectedIndex = -1; + invalidate(); + Arrays.fill(mWordWidth, 0); + Arrays.fill(mWordX, 0); + if (mPreviewPopup.isShowing()) { + mPreviewPopup.dismiss(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent me) { + + if (mGestureDetector.onTouchEvent(me)) { + return true; + } + + int action = me.getAction(); + int x = (int) me.getX(); + int y = (int) me.getY(); + mTouchX = x; + + switch (action) { + case MotionEvent.ACTION_DOWN: + mScrolled = false; + invalidate(); + break; + case MotionEvent.ACTION_MOVE: + if (y <= 0) { + // Fling up!? + if (mSelectedString != null) { + if (!mShowingCompletions) { + TextEntryState.acceptedSuggestion(mSuggestions.get(0), + mSelectedString); + } + mService.pickSuggestionManually(mSelectedIndex, mSelectedString); + mSelectedString = null; + mSelectedIndex = -1; + } + } + invalidate(); + break; + case MotionEvent.ACTION_UP: + if (!mScrolled) { + if (mSelectedString != null) { + if (!mShowingCompletions) { + TextEntryState.acceptedSuggestion(mSuggestions.get(0), + mSelectedString); + } + mService.pickSuggestionManually(mSelectedIndex, mSelectedString); + } + } + mSelectedString = null; + mSelectedIndex = -1; + removeHighlight(); + showPreview(OUT_OF_BOUNDS, null); + requestLayout(); + break; + } + return true; + } + + /** + * For flick through from keyboard, call this method with the x coordinate of the flick + * gesture. + * @param x + */ + public void takeSuggestionAt(float x) { + mTouchX = (int) x; + // To detect candidate + onDraw(null); + if (mSelectedString != null) { + if (!mShowingCompletions) { + TextEntryState.acceptedSuggestion(mSuggestions.get(0), mSelectedString); + } + mService.pickSuggestionManually(mSelectedIndex, mSelectedString); + } + invalidate(); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_REMOVE_THROUGH_PREVIEW), 200); + } + + private void showPreview(int wordIndex, String altText) { + int oldWordIndex = mCurrentWordIndex; + mCurrentWordIndex = wordIndex; + // If index changed or changing text + if (oldWordIndex != mCurrentWordIndex || altText != null) { + if (wordIndex == OUT_OF_BOUNDS) { + if (mPreviewPopup.isShowing()) { + mHandler.sendMessageDelayed(mHandler + .obtainMessage(MSG_REMOVE_PREVIEW), 60); + } + } else { + CharSequence word = altText != null? altText : mSuggestions.get(wordIndex); + mPreviewText.setText(word); + mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + int wordWidth = (int) (mPaint.measureText(word, 0, word.length()) + X_GAP * 2); + final int popupWidth = wordWidth + + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight(); + final int popupHeight = mPreviewText.getMeasuredHeight(); + //mPreviewText.setVisibility(INVISIBLE); + mPopupPreviewX = mWordX[wordIndex] - mPreviewText.getPaddingLeft() - mScrollX; + mPopupPreviewY = - popupHeight; + mHandler.removeMessages(MSG_REMOVE_PREVIEW); + int [] offsetInWindow = new int[2]; + getLocationInWindow(offsetInWindow); + if (mPreviewPopup.isShowing()) { + mPreviewPopup.update(mPopupPreviewX, mPopupPreviewY + offsetInWindow[1], + popupWidth, popupHeight); + } else { + mPreviewPopup.setWidth(popupWidth); + mPreviewPopup.setHeight(popupHeight); + mPreviewPopup.showAtLocation(this, Gravity.NO_GRAVITY, mPopupPreviewX, + mPopupPreviewY + offsetInWindow[1]); + } + mPreviewText.setVisibility(VISIBLE); + } + } + } + + private void removeHighlight() { + mTouchX = OUT_OF_BOUNDS; + invalidate(); + } + + private void longPressFirstWord() { + CharSequence word = mSuggestions.get(0); + if (mService.addWordToDictionary(word.toString())) { + showPreview(0, getContext().getResources().getString(R.string.added_word, word)); + } + } +} diff --git a/src/com/android/inputmethod/latin/CandidateViewContainer.java b/src/com/android/inputmethod/latin/CandidateViewContainer.java new file mode 100644 index 000000000..e13f2738c --- /dev/null +++ b/src/com/android/inputmethod/latin/CandidateViewContainer.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.widget.LinearLayout; + +public class CandidateViewContainer extends LinearLayout implements OnTouchListener { + + private View mButtonLeft; + private View mButtonRight; + private View mButtonLeftLayout; + private View mButtonRightLayout; + private CandidateView mCandidates; + + public CandidateViewContainer(Context screen, AttributeSet attrs) { + super(screen, attrs); + } + + public void initViews() { + if (mCandidates == null) { + mButtonLeftLayout = findViewById(R.id.candidate_left_parent); + mButtonLeft = findViewById(R.id.candidate_left); + if (mButtonLeft != null) { + mButtonLeft.setOnTouchListener(this); + } + mButtonRightLayout = findViewById(R.id.candidate_right_parent); + mButtonRight = findViewById(R.id.candidate_right); + if (mButtonRight != null) { + mButtonRight.setOnTouchListener(this); + } + mCandidates = (CandidateView) findViewById(R.id.candidates); + } + } + + @Override + public void requestLayout() { + if (mCandidates != null) { + int availableWidth = mCandidates.getWidth(); + int neededWidth = mCandidates.computeHorizontalScrollRange(); + int x = mCandidates.getScrollX(); + boolean leftVisible = x > 0; + boolean rightVisible = x + availableWidth < neededWidth; + if (mButtonLeftLayout != null) { + mButtonLeftLayout.setVisibility(leftVisible ? VISIBLE : GONE); + } + if (mButtonRightLayout != null) { + mButtonRightLayout.setVisibility(rightVisible ? VISIBLE : GONE); + } + } + super.requestLayout(); + } + + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (v == mButtonRight) { + mCandidates.scrollNext(); + } else if (v == mButtonLeft) { + mCandidates.scrollPrev(); + } + } + return false; + } + +} diff --git a/src/com/android/inputmethod/latin/Dictionary.java b/src/com/android/inputmethod/latin/Dictionary.java new file mode 100644 index 000000000..fdf34264a --- /dev/null +++ b/src/com/android/inputmethod/latin/Dictionary.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +/** + * Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key + * strokes. + */ +abstract public class Dictionary { + + /** + * Whether or not to replicate the typed word in the suggested list, even if it's valid. + */ + protected static final boolean INCLUDE_TYPED_WORD_IF_VALID = false; + + /** + * The weight to give to a word if it's length is the same as the number of typed characters. + */ + protected static final int FULL_WORD_FREQ_MULTIPLIER = 2; + + /** + * Interface to be implemented by classes requesting words to be fetched from the dictionary. + * @see #getWords(WordComposer, WordCallback) + */ + public interface WordCallback { + /** + * Adds a word to a list of suggestions. The word is expected to be ordered based on + * the provided frequency. + * @param word the character array containing the word + * @param wordOffset starting offset of the word in the character array + * @param wordLength length of valid characters in the character array + * @param frequency the frequency of occurence. This is normalized between 1 and 255, but + * can exceed those limits + * @return true if the word was added, false if no more words are required + */ + boolean addWord(char[] word, int wordOffset, int wordLength, int frequency); + } + + /** + * Searches for words in the dictionary that match the characters in the composer. Matched + * words are added through the callback object. + * @param composer the key sequence to match + * @param callback the callback object to send matched words to as possible candidates + * @see WordCallback#addWord(char[], int, int) + */ + abstract public void getWords(final WordComposer composer, final WordCallback callback); + + /** + * Checks if the given word occurs in the dictionary + * @param word the word to search for. The search should be case-insensitive. + * @return true if the word exists, false otherwise + */ + abstract public boolean isValidWord(CharSequence word); + + /** + * Compares the contents of the character array with the typed word and returns true if they + * are the same. + * @param word the array of characters that make up the word + * @param length the number of valid characters in the character array + * @param typedWord the word to compare with + * @return true if they are the same, false otherwise. + */ + protected boolean same(final char[] word, final int length, final CharSequence typedWord) { + if (typedWord.length() != length) { + return false; + } + for (int i = 0; i < length; i++) { + if (word[i] != typedWord.charAt(i)) { + return false; + } + } + return true; + } + +} diff --git a/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/src/com/android/inputmethod/latin/KeyboardSwitcher.java new file mode 100644 index 000000000..b3fcbda6f --- /dev/null +++ b/src/com/android/inputmethod/latin/KeyboardSwitcher.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.inputmethodservice.Keyboard; + +import java.util.List; + +public class KeyboardSwitcher { + + public static final int MODE_TEXT = 1; + public static final int MODE_SYMBOLS = 2; + public static final int MODE_PHONE = 3; + public static final int MODE_URL = 4; + public static final int MODE_EMAIL = 5; + public static final int MODE_IM = 6; + + public static final int MODE_TEXT_QWERTY = 0; + public static final int MODE_TEXT_ALPHA = 1; + public static final int MODE_TEXT_COUNT = 2; + + public static final int KEYBOARDMODE_NORMAL = R.id.mode_normal; + public static final int KEYBOARDMODE_URL = R.id.mode_url; + public static final int KEYBOARDMODE_EMAIL = R.id.mode_email; + public static final int KEYBOARDMODE_IM = R.id.mode_im; + + + LatinKeyboardView mInputView; + LatinIME mContext; + + private LatinKeyboard mPhoneKeyboard; + private LatinKeyboard mPhoneSymbolsKeyboard; + private LatinKeyboard mSymbolsKeyboard; + private LatinKeyboard mSymbolsShiftedKeyboard; + private LatinKeyboard mQwertyKeyboard; + private LatinKeyboard mAlphaKeyboard; + private LatinKeyboard mUrlKeyboard; + private LatinKeyboard mEmailKeyboard; + private LatinKeyboard mIMKeyboard; + + List mKeyboards; + + private int mMode; + private int mImeOptions; + private int mTextMode = MODE_TEXT_QWERTY; + + private int mLastDisplayWidth; + + KeyboardSwitcher(LatinIME context) { + mContext = context; + } + + void setInputView(LatinKeyboardView inputView) { + mInputView = inputView; + } + + void makeKeyboards() { + // Configuration change is coming after the keyboard gets recreated. So don't rely on that. + // If keyboards have already been made, check if we have a screen width change and + // create the keyboard layouts again at the correct orientation + if (mKeyboards != null) { + int displayWidth = mContext.getMaxWidth(); + if (displayWidth == mLastDisplayWidth) return; + mLastDisplayWidth = displayWidth; + } + // Delayed creation when keyboard mode is set. + mQwertyKeyboard = null; + mAlphaKeyboard = null; + mUrlKeyboard = null; + mEmailKeyboard = null; + mIMKeyboard = null; + mPhoneKeyboard = null; + mPhoneSymbolsKeyboard = null; + mSymbolsKeyboard = null; + mSymbolsShiftedKeyboard = null; + } + + void setKeyboardMode(int mode, int imeOptions) { + mMode = mode; + mImeOptions = imeOptions; + LatinKeyboard keyboard = (LatinKeyboard) mInputView.getKeyboard(); + mInputView.setPreviewEnabled(true); + switch (mode) { + case MODE_TEXT: + if (mTextMode == MODE_TEXT_QWERTY) { + if (mQwertyKeyboard == null) { + mQwertyKeyboard = new LatinKeyboard(mContext, R.xml.kbd_qwerty, + KEYBOARDMODE_NORMAL); + mQwertyKeyboard.enableShiftLock(); + } + keyboard = mQwertyKeyboard; + } else if (mTextMode == MODE_TEXT_ALPHA) { + if (mAlphaKeyboard == null) { + mAlphaKeyboard = new LatinKeyboard(mContext, R.xml.kbd_alpha, + KEYBOARDMODE_NORMAL); + mAlphaKeyboard.enableShiftLock(); + } + keyboard = mAlphaKeyboard; + } + break; + case MODE_SYMBOLS: + if (mSymbolsKeyboard == null) { + mSymbolsKeyboard = new LatinKeyboard(mContext, R.xml.kbd_symbols); + } + if (mSymbolsShiftedKeyboard == null) { + mSymbolsShiftedKeyboard = new LatinKeyboard(mContext, R.xml.kbd_symbols_shift); + } + keyboard = mSymbolsKeyboard; + break; + case MODE_PHONE: + if (mPhoneKeyboard == null) { + mPhoneKeyboard = new LatinKeyboard(mContext, R.xml.kbd_phone); + } + mInputView.setPhoneKeyboard(mPhoneKeyboard); + if (mPhoneSymbolsKeyboard == null) { + mPhoneSymbolsKeyboard = new LatinKeyboard(mContext, R.xml.kbd_phone_symbols); + } + keyboard = mPhoneKeyboard; + mInputView.setPreviewEnabled(false); + break; + case MODE_URL: + if (mUrlKeyboard == null) { + mUrlKeyboard = new LatinKeyboard(mContext, R.xml.kbd_qwerty, KEYBOARDMODE_URL); + mUrlKeyboard.enableShiftLock(); + } + keyboard = mUrlKeyboard; + break; + case MODE_EMAIL: + if (mEmailKeyboard == null) { + mEmailKeyboard = new LatinKeyboard(mContext, R.xml.kbd_qwerty, KEYBOARDMODE_EMAIL); + mEmailKeyboard.enableShiftLock(); + } + keyboard = mEmailKeyboard; + break; + case MODE_IM: + if (mIMKeyboard == null) { + mIMKeyboard = new LatinKeyboard(mContext, R.xml.kbd_qwerty, KEYBOARDMODE_IM); + mIMKeyboard.enableShiftLock(); + } + keyboard = mIMKeyboard; + break; + } + mInputView.setKeyboard(keyboard); + keyboard.setShifted(false); + keyboard.setShiftLocked(keyboard.isShiftLocked()); + keyboard.setImeOptions(mContext.getResources(), mMode, imeOptions); + } + + int getKeyboardMode() { + return mMode; + } + + boolean isTextMode() { + return mMode == MODE_TEXT; + } + + int getTextMode() { + return mTextMode; + } + + void setTextMode(int position) { + if (position < MODE_TEXT_COUNT && position >= 0) { + mTextMode = position; + } + if (isTextMode()) { + setKeyboardMode(MODE_TEXT, mImeOptions); + } + } + + int getTextModeCount() { + return MODE_TEXT_COUNT; + } + + boolean isAlphabetMode() { + Keyboard current = mInputView.getKeyboard(); + if (current == mQwertyKeyboard + || current == mAlphaKeyboard + || current == mUrlKeyboard + || current == mIMKeyboard + || current == mEmailKeyboard) { + return true; + } + return false; + } + + void toggleShift() { + Keyboard currentKeyboard = mInputView.getKeyboard(); + if (currentKeyboard == mSymbolsKeyboard) { + mSymbolsKeyboard.setShifted(true); + mInputView.setKeyboard(mSymbolsShiftedKeyboard); + mSymbolsShiftedKeyboard.setShifted(true); + mSymbolsShiftedKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions); + } else if (currentKeyboard == mSymbolsShiftedKeyboard) { + mSymbolsShiftedKeyboard.setShifted(false); + mInputView.setKeyboard(mSymbolsKeyboard); + mSymbolsKeyboard.setShifted(false); + mSymbolsKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions); + } + } + + void toggleSymbols() { + Keyboard current = mInputView.getKeyboard(); + if (mSymbolsKeyboard == null) { + mSymbolsKeyboard = new LatinKeyboard(mContext, R.xml.kbd_symbols); + } + if (mSymbolsShiftedKeyboard == null) { + mSymbolsShiftedKeyboard = new LatinKeyboard(mContext, R.xml.kbd_symbols_shift); + } + if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) { + setKeyboardMode(mMode, mImeOptions); // Could be qwerty, alpha, url, email or im + return; + } else if (current == mPhoneKeyboard) { + current = mPhoneSymbolsKeyboard; + mPhoneSymbolsKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions); + } else if (current == mPhoneSymbolsKeyboard) { + current = mPhoneKeyboard; + mPhoneKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions); + } else { + current = mSymbolsKeyboard; + mSymbolsKeyboard.setImeOptions(mContext.getResources(), mMode, mImeOptions); + } + mInputView.setKeyboard(current); + if (current == mSymbolsKeyboard) { + current.setShifted(false); + } + } +} diff --git a/src/com/android/inputmethod/latin/LatinIME.java b/src/com/android/inputmethod/latin/LatinIME.java new file mode 100644 index 000000000..8671bf2e5 --- /dev/null +++ b/src/com/android/inputmethod/latin/LatinIME.java @@ -0,0 +1,1091 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.res.Configuration; +import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.media.AudioManager; +import android.os.Debug; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.os.Vibrator; +import android.preference.PreferenceManager; +import android.text.ClipboardManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.PrintWriterPrinter; +import android.util.Printer; +import android.view.KeyEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Input method implementation for Qwerty'ish keyboard. + */ +public class LatinIME extends InputMethodService + implements KeyboardView.OnKeyboardActionListener { + static final boolean DEBUG = false; + static final boolean TRACE = false; + + private static final String PREF_VIBRATE_ON = "vibrate_on"; + private static final String PREF_SOUND_ON = "sound_on"; + private static final String PREF_PROXIMITY_CORRECTION = "hit_correction"; + private static final String PREF_PREDICTION = "prediction_mode"; + private static final String PREF_PREDICTION_LANDSCAPE = "prediction_landscape"; + private static final String PREF_AUTO_CAP = "auto_cap"; + static final String PREF_TUTORIAL_RUN = "tutorial_run"; + + private static final int MSG_UPDATE_SUGGESTIONS = 0; + private static final int MSG_CHECK_TUTORIAL = 1; + + // How many continuous deletes at which to start deleting at a higher speed. + private static final int DELETE_ACCELERATE_AT = 20; + // Key events coming any faster than this are long-presses. + private static final int QUICK_PRESS = 200; + + private static final int KEYCODE_ENTER = 10; + private static final int KEYCODE_SPACE = ' '; + + // Contextual menu positions + private static final int POS_SETTINGS = 0; + private static final int POS_METHOD = 1; + + private LatinKeyboardView mInputView; + private CandidateViewContainer mCandidateViewContainer; + private CandidateView mCandidateView; + private Suggest mSuggest; + private CompletionInfo[] mCompletions; + + private AlertDialog mOptionsDialog; + + private KeyboardSwitcher mKeyboardSwitcher; + + private UserDictionary mUserDictionary; + + private String mLocale; + + private StringBuilder mComposing = new StringBuilder(); + private WordComposer mWord = new WordComposer(); + private int mCommittedLength; + private boolean mPredicting; + private CharSequence mBestWord; + private boolean mPredictionOn; + private boolean mCompletionOn; + private boolean mPasswordMode; + private boolean mAutoSpace; + private boolean mAutoCorrectOn; + private boolean mCapsLock; + private long mLastShiftTime; + private boolean mVibrateOn; + private boolean mSoundOn; + private boolean mProximityCorrection; + private int mCorrectionMode; + private boolean mAutoCap; + private boolean mAutoPunctuate; + private boolean mTutorialShownBefore; + // Indicates whether the suggestion strip is to be on in landscape + private boolean mShowSuggestInLand; + private boolean mJustAccepted; + private CharSequence mJustRevertedSeparator; + private int mDeleteCount; + private long mLastKeyTime; + + private Tutorial mTutorial; + + private Vibrator mVibrator; + private long mVibrateDuration; + + private AudioManager mAudioManager; + private final float FX_VOLUME = 1.0f; + private boolean mSilentMode; + + private String mWordSeparators; + private String mSentenceSeparators; + + Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_SUGGESTIONS: + updateSuggestions(); + break; + case MSG_CHECK_TUTORIAL: + if (!mTutorialShownBefore) { + mTutorial = new Tutorial(mInputView); + mTutorial.start(); + } + break; + } + } + }; + + @Override public void onCreate() { + super.onCreate(); + //setStatusIcon(R.drawable.ime_qwerty); + mKeyboardSwitcher = new KeyboardSwitcher(this); + initSuggest(getResources().getConfiguration().locale.toString()); + + mVibrateDuration = getResources().getInteger(R.integer.vibrate_duration_ms); + + // register to receive ringer mode changes for silent mode + IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); + registerReceiver(mReceiver, filter); + } + + private void initSuggest(String locale) { + mLocale = locale; + mSuggest = new Suggest(this, R.raw.main); + mSuggest.setCorrectionMode(mCorrectionMode); + mUserDictionary = new UserDictionary(this); + mSuggest.setUserDictionary(mUserDictionary); + mWordSeparators = getResources().getString(R.string.word_separators); + mSentenceSeparators = getResources().getString(R.string.sentence_separators); + } + + @Override public void onDestroy() { + mUserDictionary.close(); + unregisterReceiver(mReceiver); + super.onDestroy(); + } + + @Override + public void onConfigurationChanged(Configuration conf) { + if (!TextUtils.equals(conf.locale.toString(), mLocale)) { + initSuggest(conf.locale.toString()); + } + if (!mTutorialShownBefore && mTutorial != null) { + mTutorial.close(false); + } + super.onConfigurationChanged(conf); + } + + @Override + public View onCreateInputView() { + mInputView = (LatinKeyboardView) getLayoutInflater().inflate( + R.layout.input, null); + mKeyboardSwitcher.setInputView(mInputView); + mKeyboardSwitcher.makeKeyboards(); + mInputView.setOnKeyboardActionListener(this); + mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 0); + return mInputView; + } + + @Override + public View onCreateCandidatesView() { + mKeyboardSwitcher.makeKeyboards(); + mCandidateViewContainer = (CandidateViewContainer) getLayoutInflater().inflate( + R.layout.candidates, null); + mCandidateViewContainer.initViews(); + mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates); + mCandidateView.setService(this); + setCandidatesViewShown(true); + return mCandidateViewContainer; + } + + @Override + public void onStartInputView(EditorInfo attribute, boolean restarting) { + // In landscape mode, this method gets called without the input view being created. + if (mInputView == null) { + return; + } + + mKeyboardSwitcher.makeKeyboards(); + + TextEntryState.newSession(this); + + mPredictionOn = false; + mCompletionOn = false; + mCompletions = null; + mCapsLock = false; + switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) { + case EditorInfo.TYPE_CLASS_NUMBER: + case EditorInfo.TYPE_CLASS_DATETIME: + mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, + attribute.imeOptions); + mKeyboardSwitcher.toggleSymbols(); + break; + case EditorInfo.TYPE_CLASS_PHONE: + mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE, + attribute.imeOptions); + break; + case EditorInfo.TYPE_CLASS_TEXT: + mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, + attribute.imeOptions); + //startPrediction(); + mPredictionOn = true; + // Make sure that passwords are not displayed in candidate view + int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; + if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD || + variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ) { + mPasswordMode = true; + mPredictionOn = false; + } else { + mPasswordMode = false; + } + if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) { + mAutoSpace = false; + } else { + mAutoSpace = true; + } + if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) { + mPredictionOn = false; + mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL, + attribute.imeOptions); + } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { + mPredictionOn = false; + mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL, + attribute.imeOptions); + } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) { + mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM, + attribute.imeOptions); + } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { + mPredictionOn = false; + } + if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { + mPredictionOn = false; + mCompletionOn = true && isFullscreenMode(); + } + updateShiftKeyState(attribute); + break; + default: + mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, + attribute.imeOptions); + updateShiftKeyState(attribute); + } + mInputView.closing(); + mComposing.setLength(0); + mPredicting = false; + mDeleteCount = 0; + setCandidatesViewShown(false); + if (mCandidateView != null) mCandidateView.setSuggestions(null, false, false, false); + loadSettings(); + mInputView.setProximityCorrectionEnabled(mProximityCorrection); + if (mSuggest != null) { + mSuggest.setCorrectionMode(mCorrectionMode); + } + if (!mTutorialShownBefore && mTutorial == null) { + mHandler.sendEmptyMessageDelayed(MSG_CHECK_TUTORIAL, 1000); + } + mPredictionOn = mPredictionOn && mCorrectionMode > 0; + if (TRACE) Debug.startMethodTracing("latinime"); + } + + @Override + public void onFinishInput() { + super.onFinishInput(); + + if (mInputView != null) { + mInputView.closing(); + } + if (!mTutorialShownBefore && mTutorial != null) { + mTutorial.close(false); + } + } + + @Override + public void onUpdateSelection(int oldSelStart, int oldSelEnd, + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd) { + super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, + candidatesStart, candidatesEnd); + // If the current selection in the text view changes, we should + // clear whatever candidate text we have. + if (mComposing.length() > 0 && mPredicting && (newSelStart != candidatesEnd + || newSelEnd != candidatesEnd)) { + mComposing.setLength(0); + mPredicting = false; + updateSuggestions(); + TextEntryState.reset(); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.finishComposingText(); + } + } else if (!mPredicting && !mJustAccepted + && TextEntryState.getState() == TextEntryState.STATE_ACCEPTED_DEFAULT) { + TextEntryState.reset(); + } + mJustAccepted = false; + } + + @Override + public void hideWindow() { + if (TRACE) Debug.stopMethodTracing(); + super.hideWindow(); + TextEntryState.endSession(); + } + + @Override + public void onDisplayCompletions(CompletionInfo[] completions) { + if (false) { + Log.i("foo", "Received completions:"); + for (int i=0; i<(completions != null ? completions.length : 0); i++) { + Log.i("foo", " #" + i + ": " + completions[i]); + } + } + if (mCompletionOn) { + mCompletions = completions; + if (completions == null) { + mCandidateView.setSuggestions(null, false, false, false); + return; + } + + List stringList = new ArrayList(); + for (int i=0; i<(completions != null ? completions.length : 0); i++) { + CompletionInfo ci = completions[i]; + if (ci != null) stringList.add(ci.getText()); + } + //CharSequence typedWord = mWord.getTypedWord(); + mCandidateView.setSuggestions(stringList, true, true, true); + mBestWord = null; + setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn); + } + } + + @Override + public void setCandidatesViewShown(boolean shown) { + // TODO: Remove this if we support candidates with hard keyboard + if (onEvaluateInputViewShown()) { + super.setCandidatesViewShown(shown); + } + } + + @Override + public void onComputeInsets(InputMethodService.Insets outInsets) { + super.onComputeInsets(outInsets); + outInsets.contentTopInsets = outInsets.visibleTopInsets; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + if (event.getRepeatCount() == 0 && mInputView != null) { + if (mInputView.handleBack()) { + return true; + } else if (!mTutorialShownBefore && mTutorial != null) { + mTutorial.close(true); + } + } + break; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + // Enable shift key and DPAD to do selections + if (mInputView != null && mInputView.isShown() && mInputView.isShifted()) { + event = new KeyEvent(event.getDownTime(), event.getEventTime(), + event.getAction(), event.getKeyCode(), event.getRepeatCount(), + event.getDeviceId(), event.getScanCode(), + KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) ic.sendKeyEvent(event); + return true; + } + break; + } + return super.onKeyUp(keyCode, event); + } + + private void commitTyped(InputConnection inputConnection) { + if (mPredicting) { + mPredicting = false; + if (mComposing.length() > 0) { + if (inputConnection != null) { + inputConnection.commitText(mComposing, 1); + } + mCommittedLength = mComposing.length(); + TextEntryState.acceptedTyped(mComposing); + } + updateSuggestions(); + } + } + + public void updateShiftKeyState(EditorInfo attr) { + InputConnection ic = getCurrentInputConnection(); + if (attr != null && mInputView != null && mKeyboardSwitcher.isAlphabetMode() + && ic != null) { + int caps = 0; + EditorInfo ei = getCurrentInputEditorInfo(); + if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) { + caps = ic.getCursorCapsMode(attr.inputType); + } + mInputView.setShifted(mCapsLock || caps != 0); + } + } + + private void swapPunctuationAndSpace() { + final InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + CharSequence lastTwo = ic.getTextBeforeCursor(2, 0); + if (lastTwo != null && lastTwo.length() == 2 + && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) { + ic.beginBatchEdit(); + ic.deleteSurroundingText(2, 0); + ic.commitText(lastTwo.charAt(1) + " ", 1); + ic.endBatchEdit(); + updateShiftKeyState(getCurrentInputEditorInfo()); + } + } + + private void doubleSpace() { + //if (!mAutoPunctuate) return; + if (mCorrectionMode == Suggest.CORRECTION_NONE) return; + final InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + CharSequence lastThree = ic.getTextBeforeCursor(3, 0); + if (lastThree != null && lastThree.length() == 3 + && Character.isLetterOrDigit(lastThree.charAt(0)) + && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) { + ic.beginBatchEdit(); + ic.deleteSurroundingText(2, 0); + ic.commitText(". ", 1); + ic.endBatchEdit(); + updateShiftKeyState(getCurrentInputEditorInfo()); + } + } + + public boolean addWordToDictionary(String word) { + mUserDictionary.addWord(word, 128); + return true; + } + + private boolean isAlphabet(int code) { + if (Character.isLetter(code)) { + return true; + } else { + return false; + } + } + + // Implementation of KeyboardViewListener + + public void onKey(int primaryCode, int[] keyCodes) { + long when = SystemClock.uptimeMillis(); + if (primaryCode != Keyboard.KEYCODE_DELETE || + when > mLastKeyTime + QUICK_PRESS) { + mDeleteCount = 0; + } + mLastKeyTime = when; + switch (primaryCode) { + case Keyboard.KEYCODE_DELETE: + handleBackspace(); + mDeleteCount++; + break; + case Keyboard.KEYCODE_SHIFT: + handleShift(); + break; + case Keyboard.KEYCODE_CANCEL: + if (mOptionsDialog == null || !mOptionsDialog.isShowing()) { + handleClose(); + } + break; + case LatinKeyboardView.KEYCODE_OPTIONS: + showOptionsMenu(); + break; + case LatinKeyboardView.KEYCODE_SHIFT_LONGPRESS: + if (mCapsLock) { + handleShift(); + } else { + toggleCapsLock(); + } + break; + case Keyboard.KEYCODE_MODE_CHANGE: + changeKeyboardMode(); + break; + default: + if (isWordSeparator(primaryCode)) { + handleSeparator(primaryCode); + } else { + handleCharacter(primaryCode, keyCodes); + } + // Cancel the just reverted state + mJustRevertedSeparator = null; + } + } + + public void onText(CharSequence text) { + InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + ic.beginBatchEdit(); + if (mPredicting) { + commitTyped(ic); + } + ic.commitText(text, 1); + ic.endBatchEdit(); + updateShiftKeyState(getCurrentInputEditorInfo()); + mJustRevertedSeparator = null; + } + + private void handleBackspace() { + boolean deleteChar = false; + InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + if (mPredicting) { + final int length = mComposing.length(); + if (length > 0) { + mComposing.delete(length - 1, length); + mWord.deleteLast(); + ic.setComposingText(mComposing, 1); + if (mComposing.length() == 0) { + mPredicting = false; + } + postUpdateSuggestions(); + } else { + ic.deleteSurroundingText(1, 0); + } + } else { + //getCurrentInputConnection().deleteSurroundingText(1, 0); + deleteChar = true; + //sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + TextEntryState.backspace(); + if (TextEntryState.getState() == TextEntryState.STATE_UNDO_COMMIT) { + revertLastWord(deleteChar); + return; + } else if (deleteChar) { + sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); + if (mDeleteCount > DELETE_ACCELERATE_AT) { + sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); + } + } + mJustRevertedSeparator = null; + } + + private void handleShift() { + Keyboard currentKeyboard = mInputView.getKeyboard(); + if (mKeyboardSwitcher.isAlphabetMode()) { + // Alphabet keyboard + checkToggleCapsLock(); + mInputView.setShifted(mCapsLock || !mInputView.isShifted()); + } else { + mKeyboardSwitcher.toggleShift(); + } + } + + private void handleCharacter(int primaryCode, int[] keyCodes) { + if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) { + if (!mPredicting) { + mPredicting = true; + mComposing.setLength(0); + mWord.reset(); + } + } + if (mInputView.isShifted()) { + primaryCode = Character.toUpperCase(primaryCode); + } + if (mPredicting) { + if (mInputView.isShifted() && mComposing.length() == 0) { + mWord.setCapitalized(true); + } + mComposing.append((char) primaryCode); + mWord.add(primaryCode, keyCodes); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.setComposingText(mComposing, 1); + } + postUpdateSuggestions(); + } else { + sendKeyChar((char)primaryCode); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + measureCps(); + TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode)); + } + + private void handleSeparator(int primaryCode) { + boolean pickedDefault = false; + // Handle separator + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.beginBatchEdit(); + } + if (mPredicting) { + // In certain languages where single quote is a separator, it's better + // not to auto correct, but accept the typed word. For instance, + // in Italian dov' should not be expanded to dove' because the elision + // requires the last vowel to be removed. + if (mAutoCorrectOn && primaryCode != '\'' && + (mJustRevertedSeparator == null + || mJustRevertedSeparator.length() == 0 + || mJustRevertedSeparator.charAt(0) != primaryCode)) { + pickDefaultSuggestion(); + pickedDefault = true; + } else { + commitTyped(ic); + } + } + sendKeyChar((char)primaryCode); + TextEntryState.typedCharacter((char) primaryCode, true); + if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED + && primaryCode != KEYCODE_ENTER) { + swapPunctuationAndSpace(); + } else if (isPredictionOn() && primaryCode == ' ') { + //else if (TextEntryState.STATE_SPACE_AFTER_ACCEPTED) { + doubleSpace(); + } + if (pickedDefault && mBestWord != null) { + TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + if (ic != null) { + ic.endBatchEdit(); + } + } + + private void handleClose() { + commitTyped(getCurrentInputConnection()); + if (!mTutorialShownBefore && mTutorial != null) { + mTutorial.close(true); + } + requestHideSelf(0); + mInputView.closing(); + TextEntryState.endSession(); + } + + private void checkToggleCapsLock() { + if (mInputView.getKeyboard().isShifted()) { + toggleCapsLock(); + } + } + + private void toggleCapsLock() { + mCapsLock = !mCapsLock; + if (mKeyboardSwitcher.isAlphabetMode()) { + ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock); + } + } + + private void postUpdateSuggestions() { + mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100); + } + + private boolean isPredictionOn() { + boolean predictionOn = mPredictionOn; + //if (isFullscreenMode()) predictionOn &= mPredictionLandscape; + return predictionOn; + } + + private boolean isCandidateStripVisible() { + boolean visible = isPredictionOn() && + (!isFullscreenMode() || + mCorrectionMode == Suggest.CORRECTION_FULL || + mShowSuggestInLand); + return visible; + } + + private void updateSuggestions() { + // Check if we have a suggestion engine attached. + if (mSuggest == null || !isPredictionOn()) { + return; + } + + if (!mPredicting) { + mCandidateView.setSuggestions(null, false, false, false); + return; + } + + List stringList = mSuggest.getSuggestions(mInputView, mWord, false); + boolean correctionAvailable = mSuggest.hasMinimalCorrection(); + //|| mCorrectionMode == mSuggest.CORRECTION_FULL; + CharSequence typedWord = mWord.getTypedWord(); + // If we're in basic correct + boolean typedWordValid = mSuggest.isValidWord(typedWord); + if (mCorrectionMode == Suggest.CORRECTION_FULL) { + correctionAvailable |= typedWordValid; + } + + mCandidateView.setSuggestions(stringList, false, typedWordValid, correctionAvailable); + if (stringList.size() > 0) { + if (correctionAvailable && !typedWordValid && stringList.size() > 1) { + mBestWord = stringList.get(1); + } else { + mBestWord = typedWord; + } + } else { + mBestWord = null; + } + setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn); + } + + private void pickDefaultSuggestion() { + // Complete any pending candidate query first + if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) { + mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS); + updateSuggestions(); + } + if (mBestWord != null) { + TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord); + mJustAccepted = true; + pickSuggestion(mBestWord); + } + } + + public void pickSuggestionManually(int index, CharSequence suggestion) { + if (mCompletionOn && mCompletions != null && index >= 0 + && index < mCompletions.length) { + CompletionInfo ci = mCompletions[index]; + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.commitCompletion(ci); + } + mCommittedLength = suggestion.length(); + if (mCandidateView != null) { + mCandidateView.clear(); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + return; + } + pickSuggestion(suggestion); + TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion); + // Follow it with a space + if (mAutoSpace) { + sendSpace(); + } + // Fool the state watcher so that a subsequent backspace will not do a revert + TextEntryState.typedCharacter((char) KEYCODE_SPACE, true); + } + + private void pickSuggestion(CharSequence suggestion) { + if (mCapsLock) { + suggestion = suggestion.toString().toUpperCase(); + } else if (preferCapitalization() + || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted())) { + suggestion = Character.toUpperCase(suggestion.charAt(0)) + + suggestion.subSequence(1, suggestion.length()).toString(); + } + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.commitText(suggestion, 1); + } + mPredicting = false; + mCommittedLength = suggestion.length(); + if (mCandidateView != null) { + mCandidateView.setSuggestions(null, false, false, false); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + } + + private boolean isCursorTouchingWord() { + InputConnection ic = getCurrentInputConnection(); + if (ic == null) return false; + CharSequence toLeft = ic.getTextBeforeCursor(1, 0); + CharSequence toRight = ic.getTextAfterCursor(1, 0); + if (!TextUtils.isEmpty(toLeft) + && !isWordSeparator(toLeft.charAt(0))) { + return true; + } + if (!TextUtils.isEmpty(toRight) + && !isWordSeparator(toRight.charAt(0))) { + return true; + } + return false; + } + + public void revertLastWord(boolean deleteChar) { + final int length = mComposing.length(); + if (!mPredicting && length > 0) { + final InputConnection ic = getCurrentInputConnection(); + mPredicting = true; + ic.beginBatchEdit(); + mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0); + if (deleteChar) ic.deleteSurroundingText(1, 0); + int toDelete = mCommittedLength; + CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0); + if (toTheLeft != null && toTheLeft.length() > 0 + && isWordSeparator(toTheLeft.charAt(0))) { + toDelete--; + } + ic.deleteSurroundingText(toDelete, 0); + ic.setComposingText(mComposing, 1); + TextEntryState.backspace(); + ic.endBatchEdit(); + postUpdateSuggestions(); + } else { + sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); + mJustRevertedSeparator = null; + } + } + + protected String getWordSeparators() { + return mWordSeparators; + } + + public boolean isWordSeparator(int code) { + String separators = getWordSeparators(); + return separators.contains(String.valueOf((char)code)); + } + + public boolean isSentenceSeparator(int code) { + return mSentenceSeparators.contains(String.valueOf((char)code)); + } + + private void sendSpace() { + sendKeyChar((char)KEYCODE_SPACE); + updateShiftKeyState(getCurrentInputEditorInfo()); + //onKey(KEY_SPACE[0], KEY_SPACE); + } + + public boolean preferCapitalization() { + return mWord.isCapitalized(); + } + + public void swipeRight() { + if (LatinKeyboardView.DEBUG_AUTO_PLAY) { + ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)); + CharSequence text = cm.getText(); + if (!TextUtils.isEmpty(text)) { + mInputView.startPlaying(text.toString()); + } + } +// if (mAutoCorrectOn) { +// commitTyped(getCurrentInputConnection()); +// } else if (mPredicting) { +// pickDefaultSuggestion(); +// } +// if (mAutoSpace) { +// sendSpace(); +// } + } + + public void swipeLeft() { + //handleBackspace(); + } + + public void swipeDown() { + //handleClose(); + } + + public void swipeUp() { + //launchSettings(); + } + + public void onPress(int primaryCode) { + vibrate(); + playKeyClick(primaryCode); + } + + public void onRelease(int primaryCode) { + //vibrate(); + } + + // receive ringer mode changes to detect silent mode + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateRingerMode(); + } + }; + + // update flags for silent mode + private void updateRingerMode() { + if (mAudioManager == null) { + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + } + if (mAudioManager != null) { + mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); + } + } + + private void playKeyClick(int primaryCode) { + // if mAudioManager is null, we don't have the ringer state yet + // mAudioManager will be set by updateRingerMode + if (mAudioManager == null) { + if (mInputView != null) { + updateRingerMode(); + } + } + if (mSoundOn && !mSilentMode) { + // FIXME: Volume and enable should come from UI settings + // FIXME: These should be triggered after auto-repeat logic + int sound = AudioManager.FX_KEYPRESS_STANDARD; + switch (primaryCode) { + case Keyboard.KEYCODE_DELETE: + sound = AudioManager.FX_KEYPRESS_DELETE; + break; + case KEYCODE_ENTER: + sound = AudioManager.FX_KEYPRESS_RETURN; + break; + case KEYCODE_SPACE: + sound = AudioManager.FX_KEYPRESS_SPACEBAR; + break; + } + mAudioManager.playSoundEffect(sound, FX_VOLUME); + } + } + + private void vibrate() { + if (!mVibrateOn) { + return; + } + if (mVibrator == null) { + mVibrator = new Vibrator(); + } + mVibrator.vibrate(mVibrateDuration); + } + + private void launchSettings() { + handleClose(); + Intent intent = new Intent(); + intent.setClass(LatinIME.this, LatinIMESettings.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + private void loadSettings() { + // Get the settings preferences + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + mProximityCorrection = sp.getBoolean(PREF_PROXIMITY_CORRECTION, true); + mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, true); + mSoundOn = sp.getBoolean(PREF_SOUND_ON, false); + String predictionBasic = getString(R.string.prediction_basic); + String mode = sp.getString(PREF_PREDICTION, predictionBasic); + if (mode.equals(getString(R.string.prediction_full))) { + mCorrectionMode = 2; + } else if (mode.equals(predictionBasic)) { + mCorrectionMode = 1; + } else { + mCorrectionMode = 0; + } + mAutoCorrectOn = mSuggest != null && mCorrectionMode > 0; + + mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true); + //mAutoPunctuate = sp.getBoolean(PREF_AUTO_PUNCTUATE, mCorrectionMode > 0); + mShowSuggestInLand = !sp.getBoolean(PREF_PREDICTION_LANDSCAPE, false); + mTutorialShownBefore = sp.getBoolean(PREF_TUTORIAL_RUN, false); + } + + private void showOptionsMenu() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setCancelable(true); + builder.setIcon(R.drawable.ic_dialog_keyboard); + builder.setNegativeButton(android.R.string.cancel, null); + CharSequence itemSettings = getString(R.string.english_ime_settings); + CharSequence itemInputMethod = getString(com.android.internal.R.string.inputMethod); + builder.setItems(new CharSequence[] { + itemSettings, itemInputMethod}, + new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface di, int position) { + di.dismiss(); + switch (position) { + case POS_SETTINGS: + launchSettings(); + break; + case POS_METHOD: + InputMethodManager.getInstance(LatinIME.this).showInputMethodPicker(); + break; + } + } + }); + builder.setTitle(getResources().getString(R.string.english_ime_name)); + mOptionsDialog = builder.create(); + Window window = mOptionsDialog.getWindow(); + WindowManager.LayoutParams lp = window.getAttributes(); + lp.token = mInputView.getWindowToken(); + lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; + window.setAttributes(lp); + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + mOptionsDialog.show(); + } + + private void changeKeyboardMode() { + mKeyboardSwitcher.toggleSymbols(); + if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) { + ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock); + } + + updateShiftKeyState(getCurrentInputEditorInfo()); + } + + @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + super.dump(fd, fout, args); + + final Printer p = new PrintWriterPrinter(fout); + p.println("LatinIME state :"); + p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode()); + p.println(" mCapsLock=" + mCapsLock); + p.println(" mComposing=" + mComposing.toString()); + p.println(" mPredictionOn=" + mPredictionOn); + p.println(" mCorrectionMode=" + mCorrectionMode); + p.println(" mPredicting=" + mPredicting); + p.println(" mAutoCorrectOn=" + mAutoCorrectOn); + p.println(" mAutoSpace=" + mAutoSpace); + p.println(" mCompletionOn=" + mCompletionOn); + p.println(" TextEntryState.state=" + TextEntryState.getState()); + p.println(" mSoundOn=" + mSoundOn); + p.println(" mVibrateOn=" + mVibrateOn); + } + + + private static final int[] KEY_SPACE = { KEYCODE_SPACE }; + + + // Characters per second measurement + + private static final boolean PERF_DEBUG = false; + private long mLastCpsTime; + private static final int CPS_BUFFER_SIZE = 16; + private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE]; + private int mCpsIndex; + + private void measureCps() { + if (!LatinIME.PERF_DEBUG) return; + long now = System.currentTimeMillis(); + if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial + mCpsIntervals[mCpsIndex] = now - mLastCpsTime; + mLastCpsTime = now; + mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE; + long total = 0; + for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i]; + System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total)); + } + +} + + + diff --git a/src/com/android/inputmethod/latin/LatinIMESettings.java b/src/com/android/inputmethod/latin/LatinIMESettings.java new file mode 100644 index 000000000..2c23263ea --- /dev/null +++ b/src/com/android/inputmethod/latin/LatinIMESettings.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceGroup; + +public class LatinIMESettings extends PreferenceActivity + implements OnSharedPreferenceChangeListener{ + + private static final String CORRECTION_MODE_KEY = "prediction_mode"; + private static final String PREDICTION_SETTINGS_KEY = "prediction_settings"; + private static final String PREDICTION_LANDSCAPE_KEY = "prediction_landscape"; + + private ListPreference mCorrectionMode; + private PreferenceGroup mPredictionSettings; + private Preference mPredictionLandscape; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.prefs); + mCorrectionMode = (ListPreference) findPreference(CORRECTION_MODE_KEY); + mPredictionSettings = (PreferenceGroup) findPreference(PREDICTION_SETTINGS_KEY); + mPredictionLandscape = findPreference(PREDICTION_LANDSCAPE_KEY); + updatePredictionSettings(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + protected void onDestroy() { + getPreferenceScreen().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + super.onDestroy(); + } + + private void updatePredictionSettings() { + if (mCorrectionMode != null && mPredictionSettings != null) { + String correctionMode = mCorrectionMode.getValue(); + if (correctionMode.equals(getResources().getString(R.string.prediction_none))) { + mPredictionSettings.setEnabled(false); + } else { + mPredictionSettings.setEnabled(true); + boolean suggestionsInLandscape = + !correctionMode.equals(getResources().getString(R.string.prediction_full)); + mPredictionLandscape.setEnabled(suggestionsInLandscape); + } + } + } + + public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { + if (key.equals(CORRECTION_MODE_KEY)) { + updatePredictionSettings(); + } + } +} diff --git a/src/com/android/inputmethod/latin/LatinKeyboard.java b/src/com/android/inputmethod/latin/LatinKeyboard.java new file mode 100644 index 000000000..94b72b885 --- /dev/null +++ b/src/com/android/inputmethod/latin/LatinKeyboard.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.inputmethodservice.Keyboard; +import android.view.inputmethod.EditorInfo; + +public class LatinKeyboard extends Keyboard { + + private Drawable mShiftLockIcon; + private Drawable mShiftLockPreviewIcon; + private Drawable mOldShiftIcon; + private Drawable mOldShiftPreviewIcon; + private Key mShiftKey; + private Key mEnterKey; + + private static final int SHIFT_OFF = 0; + private static final int SHIFT_ON = 1; + private static final int SHIFT_LOCKED = 2; + + private int mShiftState = SHIFT_OFF; + + public LatinKeyboard(Context context, int xmlLayoutResId) { + this(context, xmlLayoutResId, 0); + } + + public LatinKeyboard(Context context, int xmlLayoutResId, int mode) { + super(context, xmlLayoutResId, mode); + mShiftLockIcon = context.getResources() + .getDrawable(R.drawable.sym_keyboard_shift_locked); + mShiftLockPreviewIcon = context.getResources() + .getDrawable(R.drawable.sym_keyboard_feedback_shift_locked); + mShiftLockPreviewIcon.setBounds(0, 0, + mShiftLockPreviewIcon.getIntrinsicWidth(), + mShiftLockPreviewIcon.getIntrinsicHeight()); + } + + public LatinKeyboard(Context context, int layoutTemplateResId, + CharSequence characters, int columns, int horizontalPadding) { + super(context, layoutTemplateResId, characters, columns, horizontalPadding); + } + + @Override + protected Key createKeyFromXml(Resources res, Row parent, int x, int y, + XmlResourceParser parser) { + Key key = new LatinKey(res, parent, x, y, parser); + if (key.codes[0] == 10) { + mEnterKey = key; + } + return key; + } + + void setImeOptions(Resources res, int mode, int options) { + if (mEnterKey != null) { + // Reset some of the rarely used attributes. + mEnterKey.popupCharacters = null; + mEnterKey.popupResId = 0; + mEnterKey.text = null; + switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) { + case EditorInfo.IME_ACTION_GO: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_go_key); + break; + case EditorInfo.IME_ACTION_NEXT: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_next_key); + break; + case EditorInfo.IME_ACTION_DONE: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_done_key); + break; + case EditorInfo.IME_ACTION_SEARCH: + mEnterKey.iconPreview = res.getDrawable( + R.drawable.sym_keyboard_feedback_search); + mEnterKey.icon = res.getDrawable( + R.drawable.sym_keyboard_search); + mEnterKey.label = null; + break; + case EditorInfo.IME_ACTION_SEND: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_send_key); + break; + default: + if (mode == KeyboardSwitcher.MODE_IM) { + mEnterKey.icon = null; + mEnterKey.iconPreview = null; + mEnterKey.label = ":-)"; + mEnterKey.text = ":-) "; + mEnterKey.popupResId = R.xml.popup_smileys; + } else { + mEnterKey.iconPreview = res.getDrawable( + R.drawable.sym_keyboard_feedback_return); + mEnterKey.icon = res.getDrawable( + R.drawable.sym_keyboard_return); + mEnterKey.label = null; + } + break; + } + // Set the initial size of the preview icon + if (mEnterKey.iconPreview != null) { + mEnterKey.iconPreview.setBounds(0, 0, + mEnterKey.iconPreview.getIntrinsicWidth(), + mEnterKey.iconPreview.getIntrinsicHeight()); + } + } + } + + void enableShiftLock() { + int index = getShiftKeyIndex(); + if (index >= 0) { + mShiftKey = getKeys().get(index); + if (mShiftKey instanceof LatinKey) { + ((LatinKey)mShiftKey).enableShiftLock(); + } + mOldShiftIcon = mShiftKey.icon; + mOldShiftPreviewIcon = mShiftKey.iconPreview; + } + } + + void setShiftLocked(boolean shiftLocked) { + if (mShiftKey != null) { + if (shiftLocked) { + mShiftKey.on = true; + mShiftKey.icon = mShiftLockIcon; + mShiftState = SHIFT_LOCKED; + } else { + mShiftKey.on = false; + mShiftKey.icon = mShiftLockIcon; + mShiftState = SHIFT_ON; + } + } + } + + boolean isShiftLocked() { + return mShiftState == SHIFT_LOCKED; + } + + @Override + public boolean setShifted(boolean shiftState) { + boolean shiftChanged = false; + if (mShiftKey != null) { + if (shiftState == false) { + shiftChanged = mShiftState != SHIFT_OFF; + mShiftState = SHIFT_OFF; + mShiftKey.on = false; + mShiftKey.icon = mOldShiftIcon; + } else { + if (mShiftState == SHIFT_OFF) { + shiftChanged = mShiftState == SHIFT_OFF; + mShiftState = SHIFT_ON; + mShiftKey.icon = mShiftLockIcon; + } + } + } else { + return super.setShifted(shiftState); + } + return shiftChanged; + } + + @Override + public boolean isShifted() { + if (mShiftKey != null) { + return mShiftState != SHIFT_OFF; + } else { + return super.isShifted(); + } + } + + static class LatinKey extends Keyboard.Key { + + private boolean mShiftLockEnabled; + + public LatinKey(Resources res, Keyboard.Row parent, int x, int y, + XmlResourceParser parser) { + super(res, parent, x, y, parser); + } + + void enableShiftLock() { + mShiftLockEnabled = true; + } + + @Override + public void onReleased(boolean inside) { + if (!mShiftLockEnabled) { + super.onReleased(inside); + } else { + pressed = !pressed; + } + } + + /** + * Overriding this method so that we can reduce the target area for certain keys. + */ + @Override + public boolean isInside(int x, int y) { + if ((edgeFlags & Keyboard.EDGE_BOTTOM) != 0 || + codes[0] == KEYCODE_SHIFT || + codes[0] == KEYCODE_DELETE) { + y -= height / 10; + } + if (codes[0] == KEYCODE_SHIFT) x += width / 6; + if (codes[0] == KEYCODE_DELETE) x -= width / 6; + return super.isInside(x, y); + } + } +} diff --git a/src/com/android/inputmethod/latin/LatinKeyboardView.java b/src/com/android/inputmethod/latin/LatinKeyboardView.java new file mode 100644 index 000000000..363dcd0b0 --- /dev/null +++ b/src/com/android/inputmethod/latin/LatinKeyboardView.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.content.Context; +import android.graphics.Canvas; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.inputmethodservice.Keyboard.Key; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import java.util.List; + +public class LatinKeyboardView extends KeyboardView { + + static final int KEYCODE_OPTIONS = -100; + static final int KEYCODE_SHIFT_LONGPRESS = -101; + + private Keyboard mPhoneKeyboard; + + public LatinKeyboardView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setPhoneKeyboard(Keyboard phoneKeyboard) { + mPhoneKeyboard = phoneKeyboard; + } + + @Override + protected boolean onLongPress(Key key) { + if (key.codes[0] == Keyboard.KEYCODE_MODE_CHANGE) { + getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null); + return true; + } else if (key.codes[0] == Keyboard.KEYCODE_SHIFT) { + getOnKeyboardActionListener().onKey(KEYCODE_SHIFT_LONGPRESS, null); + invalidate(); + return true; + } else if (key.codes[0] == '0' && getKeyboard() == mPhoneKeyboard) { + // Long pressing on 0 in phone number keypad gives you a '+'. + getOnKeyboardActionListener().onKey('+', null); + return true; + } else { + return super.onLongPress(key); + } + } + + + /**************************** INSTRUMENTATION *******************************/ + + static final boolean DEBUG_AUTO_PLAY = false; + private static final int MSG_TOUCH_DOWN = 1; + private static final int MSG_TOUCH_UP = 2; + + Handler mHandler2; + + private String mStringToPlay; + private int mStringIndex; + private boolean mDownDelivered; + private Key[] mAsciiKeys = new Key[256]; + private boolean mPlaying; + + @Override + public void setKeyboard(Keyboard k) { + super.setKeyboard(k); + if (DEBUG_AUTO_PLAY) { + findKeys(); + if (mHandler2 == null) { + mHandler2 = new Handler() { + @Override + public void handleMessage(Message msg) { + removeMessages(MSG_TOUCH_DOWN); + removeMessages(MSG_TOUCH_UP); + if (mPlaying == false) return; + + switch (msg.what) { + case MSG_TOUCH_DOWN: + if (mStringIndex >= mStringToPlay.length()) { + mPlaying = false; + return; + } + char c = mStringToPlay.charAt(mStringIndex); + while (c > 255 || mAsciiKeys[(int) c] == null) { + mStringIndex++; + if (mStringIndex >= mStringToPlay.length()) { + mPlaying = false; + return; + } + c = mStringToPlay.charAt(mStringIndex); + } + int x = mAsciiKeys[c].x + 10; + int y = mAsciiKeys[c].y + 26; + MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_DOWN, x, y, 0); + LatinKeyboardView.this.dispatchTouchEvent(me); + me.recycle(); + sendEmptyMessageDelayed(MSG_TOUCH_UP, 500); // Deliver up in 500ms if nothing else + // happens + mDownDelivered = true; + break; + case MSG_TOUCH_UP: + char cUp = mStringToPlay.charAt(mStringIndex); + int x2 = mAsciiKeys[cUp].x + 10; + int y2 = mAsciiKeys[cUp].y + 26; + mStringIndex++; + + MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_UP, x2, y2, 0); + LatinKeyboardView.this.dispatchTouchEvent(me2); + me2.recycle(); + sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 500); // Deliver up in 500ms if nothing else + // happens + mDownDelivered = false; + break; + } + } + }; + + } + } + } + + private void findKeys() { + List keys = getKeyboard().getKeys(); + // Get the keys on this keyboard + for (int i = 0; i < keys.size(); i++) { + int code = keys.get(i).codes[0]; + if (code >= 0 && code <= 255) { + mAsciiKeys[code] = keys.get(i); + } + } + } + + void startPlaying(String s) { + if (!DEBUG_AUTO_PLAY) return; + if (s == null) return; + mStringToPlay = s.toLowerCase(); + mPlaying = true; + mDownDelivered = false; + mStringIndex = 0; + mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 10); + } + + @Override + public void draw(Canvas c) { + super.draw(c); + if (DEBUG_AUTO_PLAY && mPlaying) { + mHandler2.removeMessages(MSG_TOUCH_DOWN); + mHandler2.removeMessages(MSG_TOUCH_UP); + if (mDownDelivered) { + mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_UP, 20); + } else { + mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 20); + } + } + } +} diff --git a/src/com/android/inputmethod/latin/Suggest.java b/src/com/android/inputmethod/latin/Suggest.java new file mode 100755 index 000000000..91decd66a --- /dev/null +++ b/src/com/android/inputmethod/latin/Suggest.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.content.Context; +import android.text.AutoText; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This class loads a dictionary and provides a list of suggestions for a given sequence of + * characters. This includes corrections and completions. + * @hide pending API Council Approval + */ +public class Suggest implements Dictionary.WordCallback { + + public static final int CORRECTION_NONE = 0; + public static final int CORRECTION_BASIC = 1; + public static final int CORRECTION_FULL = 2; + + private Dictionary mMainDict; + + private Dictionary mUserDictionary; + + private int mPrefMaxSuggestions = 12; + + private int[] mPriorities = new int[mPrefMaxSuggestions]; + private List mSuggestions = new ArrayList(); + private boolean mIncludeTypedWordIfValid; + private List mStringPool = new ArrayList(); + private Context mContext; + private boolean mHaveCorrection; + private CharSequence mOriginalWord; + private String mLowerOriginalWord; + + private int mCorrectionMode = CORRECTION_BASIC; + + + public Suggest(Context context, int dictionaryResId) { + mContext = context; + mMainDict = new BinaryDictionary(context, dictionaryResId); + for (int i = 0; i < mPrefMaxSuggestions; i++) { + StringBuilder sb = new StringBuilder(32); + mStringPool.add(sb); + } + } + + public int getCorrectionMode() { + return mCorrectionMode; + } + + public void setCorrectionMode(int mode) { + mCorrectionMode = mode; + } + + /** + * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted + * before the main dictionary, if set. + */ + public void setUserDictionary(Dictionary userDictionary) { + mUserDictionary = userDictionary; + } + + /** + * Number of suggestions to generate from the input key sequence. This has + * to be a number between 1 and 100 (inclusive). + * @param maxSuggestions + * @throws IllegalArgumentException if the number is out of range + */ + public void setMaxSuggestions(int maxSuggestions) { + if (maxSuggestions < 1 || maxSuggestions > 100) { + throw new IllegalArgumentException("maxSuggestions must be between 1 and 100"); + } + mPrefMaxSuggestions = maxSuggestions; + mPriorities = new int[mPrefMaxSuggestions]; + collectGarbage(); + while (mStringPool.size() < mPrefMaxSuggestions) { + StringBuilder sb = new StringBuilder(32); + mStringPool.add(sb); + } + } + + private boolean haveSufficientCommonality(String original, CharSequence suggestion) { + final int len = Math.min(original.length(), suggestion.length()); + if (len <= 2) return true; + int matching = 0; + for (int i = 0; i < len; i++) { + if (UserDictionary.toLowerCase(original.charAt(i)) + == UserDictionary.toLowerCase(suggestion.charAt(i))) { + matching++; + } + } + if (len <= 4) { + return matching >= 2; + } else { + return matching > len / 2; + } + } + + /** + * Returns a list of words that match the list of character codes passed in. + * This list will be overwritten the next time this function is called. + * @param a view for retrieving the context for AutoText + * @param codes the list of codes. Each list item contains an array of character codes + * in order of probability where the character at index 0 in the array has the highest + * probability. + * @return list of suggestions. + */ + public List getSuggestions(View view, WordComposer wordComposer, + boolean includeTypedWordIfValid) { + mHaveCorrection = false; + collectGarbage(); + Arrays.fill(mPriorities, 0); + mIncludeTypedWordIfValid = includeTypedWordIfValid; + + // Save a lowercase version of the original word + mOriginalWord = wordComposer.getTypedWord(); + if (mOriginalWord != null) { + mOriginalWord = mOriginalWord.toString(); + mLowerOriginalWord = mOriginalWord.toString().toLowerCase(); + } else { + mLowerOriginalWord = ""; + } + // Search the dictionary only if there are at least 2 characters + if (wordComposer.size() > 1) { + if (mUserDictionary != null) { + mUserDictionary.getWords(wordComposer, this); + if (mSuggestions.size() > 0 && isValidWord(mOriginalWord)) { + mHaveCorrection = true; + } + } + mMainDict.getWords(wordComposer, this); + if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 0) { + mHaveCorrection = true; + } + } + if (mOriginalWord != null) { + mSuggestions.add(0, mOriginalWord.toString()); + } + + // Check if the first suggestion has a minimum number of characters in common + if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 1) { + if (!haveSufficientCommonality(mLowerOriginalWord, mSuggestions.get(1))) { + mHaveCorrection = false; + } + } + + int i = 0; + int max = 6; + // Don't autotext the suggestions from the dictionaries + if (mCorrectionMode == CORRECTION_BASIC) max = 1; + while (i < mSuggestions.size() && i < max) { + String suggestedWord = mSuggestions.get(i).toString().toLowerCase(); + CharSequence autoText = + AutoText.get(suggestedWord, 0, suggestedWord.length(), view); + // Is there an AutoText correction? + boolean canAdd = autoText != null; + // Is that correction already the current prediction (or original word)? + canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i)); + // Is that correction already the next predicted word? + if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) { + canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1)); + } + if (canAdd) { + mHaveCorrection = true; + mSuggestions.add(i + 1, autoText); + i++; + } + i++; + } + + return mSuggestions; + } + + public boolean hasMinimalCorrection() { + return mHaveCorrection; + } + + private boolean compareCaseInsensitive(final String mLowerOriginalWord, + final char[] word, final int offset, final int length) { + final int originalLength = mLowerOriginalWord.length(); + if (originalLength == length && Character.isUpperCase(word[offset])) { + for (int i = 0; i < originalLength; i++) { + if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) { + return false; + } + } + return true; + } + return false; + } + + public boolean addWord(final char[] word, final int offset, final int length, final int freq) { + int pos = 0; + final int[] priorities = mPriorities; + final int prefMaxSuggestions = mPrefMaxSuggestions; + // Check if it's the same word, only caps are different + if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) { + pos = 0; + } else { + // Check the last one's priority and bail + if (priorities[prefMaxSuggestions - 1] >= freq) return true; + while (pos < prefMaxSuggestions) { + if (priorities[pos] < freq + || (priorities[pos] == freq && length < mSuggestions + .get(pos).length())) { + break; + } + pos++; + } + } + + if (pos >= prefMaxSuggestions) { + return true; + } + System.arraycopy(priorities, pos, priorities, pos + 1, + prefMaxSuggestions - pos - 1); + priorities[pos] = freq; + int poolSize = mStringPool.size(); + StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) + : new StringBuilder(32); + sb.setLength(0); + sb.append(word, offset, length); + mSuggestions.add(pos, sb); + if (mSuggestions.size() > prefMaxSuggestions) { + CharSequence garbage = mSuggestions.remove(prefMaxSuggestions); + if (garbage instanceof StringBuilder) { + mStringPool.add(garbage); + } + } + return true; + } + + public boolean isValidWord(final CharSequence word) { + if (word == null || word.length() == 0) { + return false; + } + return (mCorrectionMode == CORRECTION_FULL && mMainDict.isValidWord(word)) + || (mCorrectionMode > CORRECTION_NONE && + (mUserDictionary != null && mUserDictionary.isValidWord(word))); + } + + private void collectGarbage() { + int poolSize = mStringPool.size(); + int garbageSize = mSuggestions.size(); + while (poolSize < mPrefMaxSuggestions && garbageSize > 0) { + CharSequence garbage = mSuggestions.get(garbageSize - 1); + if (garbage != null && garbage instanceof StringBuilder) { + mStringPool.add(garbage); + poolSize++; + } + garbageSize--; + } + if (poolSize == mPrefMaxSuggestions + 1) { + Log.w("Suggest", "String pool got too big: " + poolSize); + } + mSuggestions.clear(); + } +} diff --git a/src/com/android/inputmethod/latin/TextEntryState.java b/src/com/android/inputmethod/latin/TextEntryState.java new file mode 100644 index 000000000..90c364a1c --- /dev/null +++ b/src/com/android/inputmethod/latin/TextEntryState.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.content.Context; +import android.text.format.DateFormat; +import android.util.Log; + +import android.inputmethodservice.Keyboard.Key; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Calendar; + +public class TextEntryState { + + private static boolean LOGGING = false; + + private static int sBackspaceCount = 0; + + private static int sAutoSuggestCount = 0; + + private static int sAutoSuggestUndoneCount = 0; + + private static int sManualSuggestCount = 0; + + private static int sWordNotInDictionaryCount = 0; + + private static int sSessionCount = 0; + + private static int sTypedChars; + + private static int sActualChars; + + private static final String[] STATES = { + "Unknown", + "Start", + "In word", + "Accepted default", + "Picked suggestion", + "Punc. after word", + "Punc. after accepted", + "Space after accepted", + "Space after picked", + "Undo commit" + }; + + public static final int STATE_UNKNOWN = 0; + public static final int STATE_START = 1; + public static final int STATE_IN_WORD = 2; + public static final int STATE_ACCEPTED_DEFAULT = 3; + public static final int STATE_PICKED_SUGGESTION = 4; + public static final int STATE_PUNCTUATION_AFTER_WORD = 5; + public static final int STATE_PUNCTUATION_AFTER_ACCEPTED = 6; + public static final int STATE_SPACE_AFTER_ACCEPTED = 7; + public static final int STATE_SPACE_AFTER_PICKED = 8; + public static final int STATE_UNDO_COMMIT = 9; + + private static int sState = STATE_UNKNOWN; + + private static FileOutputStream sKeyLocationFile; + private static FileOutputStream sUserActionFile; + + public static void newSession(Context context) { + sSessionCount++; + sAutoSuggestCount = 0; + sBackspaceCount = 0; + sAutoSuggestUndoneCount = 0; + sManualSuggestCount = 0; + sWordNotInDictionaryCount = 0; + sTypedChars = 0; + sActualChars = 0; + sState = STATE_START; + + if (LOGGING) { + try { + sKeyLocationFile = context.openFileOutput("key.txt", Context.MODE_APPEND); + sUserActionFile = context.openFileOutput("action.txt", Context.MODE_APPEND); + } catch (IOException ioe) { + Log.e("TextEntryState", "Couldn't open file for output: " + ioe); + } + } + } + + public static void endSession() { + if (sKeyLocationFile == null) { + return; + } + try { + sKeyLocationFile.close(); + // Write to log file + // Write timestamp, settings, + String out = DateFormat.format("MM:dd hh:mm:ss", Calendar.getInstance().getTime()) + .toString() + + " BS: " + sBackspaceCount + + " auto: " + sAutoSuggestCount + + " manual: " + sManualSuggestCount + + " typed: " + sWordNotInDictionaryCount + + " undone: " + sAutoSuggestUndoneCount + + " saved: " + ((float) (sActualChars - sTypedChars) / sActualChars) + + "\n"; + sUserActionFile.write(out.getBytes()); + sUserActionFile.close(); + sKeyLocationFile = null; + sUserActionFile = null; + } catch (IOException ioe) { + + } + } + + public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) { + if (!typedWord.equals(actualWord)) { + sAutoSuggestCount++; + } + sTypedChars += typedWord.length(); + sActualChars += actualWord.length(); + sState = STATE_ACCEPTED_DEFAULT; + } + + public static void acceptedTyped(CharSequence typedWord) { + sWordNotInDictionaryCount++; + sState = STATE_PICKED_SUGGESTION; + } + + public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) { + sManualSuggestCount++; + if (typedWord.equals(actualWord)) { + acceptedTyped(typedWord); + } + sState = STATE_PICKED_SUGGESTION; + } + + public static void typedCharacter(char c, boolean isSeparator) { + boolean isSpace = c == ' '; + switch (sState) { + case STATE_IN_WORD: + if (isSpace || isSeparator) { + sState = STATE_START; + } else { + // State hasn't changed. + } + break; + case STATE_ACCEPTED_DEFAULT: + case STATE_SPACE_AFTER_PICKED: + if (isSpace) { + sState = STATE_SPACE_AFTER_ACCEPTED; + } else if (isSeparator) { + sState = STATE_PUNCTUATION_AFTER_ACCEPTED; + } else { + sState = STATE_IN_WORD; + } + break; + case STATE_PICKED_SUGGESTION: + if (isSpace) { + sState = STATE_SPACE_AFTER_PICKED; + } else if (isSeparator) { + // Swap + sState = STATE_PUNCTUATION_AFTER_ACCEPTED; + } else { + sState = STATE_IN_WORD; + } + break; + case STATE_START: + case STATE_UNKNOWN: + case STATE_SPACE_AFTER_ACCEPTED: + case STATE_PUNCTUATION_AFTER_ACCEPTED: + case STATE_PUNCTUATION_AFTER_WORD: + if (!isSpace && !isSeparator) { + sState = STATE_IN_WORD; + } else { + sState = STATE_START; + } + break; + case STATE_UNDO_COMMIT: + if (isSpace || isSeparator) { + sState = STATE_ACCEPTED_DEFAULT; + } else { + sState = STATE_IN_WORD; + } + } + } + + public static void backspace() { + if (sState == STATE_ACCEPTED_DEFAULT) { + sState = STATE_UNDO_COMMIT; + sAutoSuggestUndoneCount++; + } else if (sState == STATE_UNDO_COMMIT) { + sState = STATE_IN_WORD; + } + sBackspaceCount++; + } + + public static void reset() { + sState = STATE_START; + } + + public static int getState() { + return sState; + } + + public static void keyPressedAt(Key key, int x, int y) { + if (LOGGING && sKeyLocationFile != null && key.codes[0] >= 32) { + String out = + "KEY: " + (char) key.codes[0] + + " X: " + x + + " Y: " + y + + " MX: " + (key.x + key.width / 2) + + " MY: " + (key.y + key.height / 2) + + "\n"; + try { + sKeyLocationFile.write(out.getBytes()); + } catch (IOException ioe) { + // TODO: May run out of space + } + } + } +} + diff --git a/src/com/android/inputmethod/latin/Tutorial.java b/src/com/android/inputmethod/latin/Tutorial.java new file mode 100644 index 000000000..2b3138bf9 --- /dev/null +++ b/src/com/android/inputmethod/latin/Tutorial.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.graphics.drawable.Drawable; +import android.opengl.Visibility; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.preference.PreferenceManager; +import android.text.Layout; +import android.text.StaticLayout; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.PopupWindow; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +public class Tutorial { + + private List mBubbles = new ArrayList(); + private long mStartTime; + private static final long MINIMUM_TIME = 6000; + private static final long MAXIMUM_TIME = 20000; + private View mInputView; + private int[] mLocation = new int[2]; + private int mBubblePointerOffset; + + private static final int MSG_SHOW_BUBBLE = 0; + private static final int MSG_HIDE_ALL = 1; + + Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SHOW_BUBBLE: + Bubble bubba = (Bubble) msg.obj; + bubba.show(mLocation[0], mLocation[1]); + break; + case MSG_HIDE_ALL: + close(true); + } + } + }; + + class Bubble { + Drawable bubbleBackground; + int x; + int y; + int width; + int gravity; + String text; + boolean dismissOnTouch; + boolean dismissOnClose; + PopupWindow window; + TextView textView; + View inputView; + + Bubble(Context context, View inputView, + int backgroundResource, int bx, int by, int bw, int gravity, int textResource, + boolean dismissOnTouch, boolean dismissOnClose) { + bubbleBackground = context.getResources().getDrawable(backgroundResource); + x = bx; + y = by; + width = bw; + this.gravity = gravity; + text = context.getResources().getString(textResource); + this.dismissOnTouch = dismissOnTouch; + this.dismissOnClose = dismissOnClose; + this.inputView = inputView; + window = new PopupWindow(context); + window.setBackgroundDrawable(null); + LayoutInflater inflate = + (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + textView = (TextView) inflate.inflate(R.layout.bubble_text, null); + textView.setBackgroundDrawable(bubbleBackground); + textView.setText(text); + window.setContentView(textView); + window.setFocusable(false); + window.setTouchable(true); + window.setOutsideTouchable(false); + textView.setOnTouchListener(new View.OnTouchListener() { + public boolean onTouch(View view, MotionEvent me) { + Tutorial.this.touched(); + return true; + } + }); + } + + private void chooseSize(PopupWindow pop, View parentView, CharSequence text, TextView tv) { + int wid = tv.getPaddingLeft() + tv.getPaddingRight(); + int ht = tv.getPaddingTop() + tv.getPaddingBottom(); + + /* + * Figure out how big the text would be if we laid it out to the + * full width of this view minus the border. + */ + int cap = width - wid; + + Layout l = new StaticLayout(text, tv.getPaint(), cap, + Layout.Alignment.ALIGN_NORMAL, 1, 0, true); + float max = 0; + for (int i = 0; i < l.getLineCount(); i++) { + max = Math.max(max, l.getLineWidth(i)); + } + + /* + * Now set the popup size to be big enough for the text plus the border. + */ + pop.setWidth(width); + pop.setHeight(ht + l.getHeight()); + } + + void show(int offx, int offy) { + chooseSize(window, inputView, text, textView); + if (inputView.getVisibility() == View.VISIBLE + && inputView.getWindowVisibility() == View.VISIBLE) { + try { + if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) offy -= window.getHeight(); + if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) offx -= window.getWidth(); + window.showAtLocation(inputView, Gravity.NO_GRAVITY, x + offx, y + offy); + } catch (Exception e) { + // Input view is not valid + } + } + } + + void hide() { + textView.setOnTouchListener(null); + if (window.isShowing()) { + window.dismiss(); + } + } + } + + public Tutorial(LatinKeyboardView inputView) { + Context context = inputView.getContext(); + int inputHeight = inputView.getHeight(); + int inputWidth = inputView.getWidth(); + mBubblePointerOffset = inputView.getContext().getResources() + .getDimensionPixelOffset(R.dimen.bubble_pointer_offset); + Bubble b0 = new Bubble(context, inputView, + R.drawable.dialog_bubble_step02, 0, 0, + inputWidth, + Gravity.BOTTOM | Gravity.LEFT, + R.string.tip_dismiss, + false, true); + mBubbles.add(b0); + Bubble b1 = new Bubble(context, inputView, + R.drawable.dialog_bubble_step03, + (int) (inputWidth * 0.85) + mBubblePointerOffset, inputHeight / 5, + (int) (inputWidth * 0.45), + Gravity.TOP | Gravity.RIGHT, + R.string.tip_long_press, + true, false); + mBubbles.add(b1); + Bubble b2 = new Bubble(inputView.getContext(), inputView, + R.drawable.dialog_bubble_step04, + inputWidth / 10 - mBubblePointerOffset, inputHeight - inputHeight / 5, + (int) (inputWidth * 0.45), + Gravity.BOTTOM | Gravity.LEFT, + R.string.tip_access_symbols, + true, false); + mBubbles.add(b2); + mInputView = inputView; + } + + void start() { + mInputView.getLocationInWindow(mLocation); + long delayMillis = 0; + for (int i = 0; i < mBubbles.size(); i++) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_BUBBLE, mBubbles.get(i)), delayMillis); + delayMillis += 2000; + } + //mHandler.sendEmptyMessageDelayed(MSG_HIDE_ALL, MAXIMUM_TIME); + mStartTime = SystemClock.uptimeMillis(); + } + + void touched() { + if (SystemClock.uptimeMillis() - mStartTime < MINIMUM_TIME) { + return; + } + for (int i = 0; i < mBubbles.size(); i++) { + Bubble bubba = mBubbles.get(i); + if (bubba.dismissOnTouch) { + bubba.hide(); + } + } + } + + void close(boolean completed) { + mHandler.removeMessages(MSG_SHOW_BUBBLE); + for (int i = 0; i < mBubbles.size(); i++) { + mBubbles.get(i).hide(); + } + if (completed) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( + mInputView.getContext()); + Editor editor = sp.edit(); + editor.putBoolean(LatinIME.PREF_TUTORIAL_RUN, true); + editor.commit(); + } + } +} diff --git a/src/com/android/inputmethod/latin/UserDictionary.java b/src/com/android/inputmethod/latin/UserDictionary.java new file mode 100644 index 000000000..09549bf8c --- /dev/null +++ b/src/com/android/inputmethod/latin/UserDictionary.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.provider.UserDictionary.Words; + +public class UserDictionary extends Dictionary { + + private static final String[] PROJECTION = { + Words._ID, + Words.WORD, + Words.FREQUENCY + }; + + private static final int INDEX_WORD = 1; + private static final int INDEX_FREQUENCY = 2; + + private static final char QUOTE = '\''; + + private Context mContext; + + List mRoots; + private int mMaxDepth; + private int mInputLength; + + public static final int MAX_WORD_LENGTH = 32; + + private char[] mWordBuilder = new char[MAX_WORD_LENGTH]; + + private ContentObserver mObserver; + + static class Node { + char code; + int frequency; + boolean terminal; + List children; + } + + private boolean mRequiresReload; + + public UserDictionary(Context context) { + mContext = context; + // Perform a managed query. The Activity will handle closing and requerying the cursor + // when needed. + ContentResolver cres = context.getContentResolver(); + + cres.registerContentObserver(Words.CONTENT_URI, true, mObserver = new ContentObserver(null) { + @Override + public void onChange(boolean self) { + mRequiresReload = true; + } + }); + + loadDictionary(); + } + + public synchronized void close() { + if (mObserver != null) { + mContext.getContentResolver().unregisterContentObserver(mObserver); + mObserver = null; + } + } + + private synchronized void loadDictionary() { + Cursor cursor = mContext.getContentResolver() + .query(Words.CONTENT_URI, PROJECTION, "(locale IS NULL) or (locale=?)", + new String[] { Locale.getDefault().toString() }, null); + addWords(cursor); + mRequiresReload = false; + } + + /** + * Adds a word to the dictionary and makes it persistent. + * @param word the word to add. If the word is capitalized, then the dictionary will + * recognize it as a capitalized word when searched. + * @param frequency the frequency of occurrence of the word. A frequency of 255 is considered + * the highest. + * @TODO use a higher or float range for frequency + */ + public synchronized void addWord(String word, int frequency) { + if (mRequiresReload) loadDictionary(); + // Safeguard against adding long words. Can cause stack overflow. + if (word.length() >= MAX_WORD_LENGTH) return; + addWordRec(mRoots, word, 0, frequency); + Words.addWord(mContext, word, frequency, Words.LOCALE_TYPE_CURRENT); + // In case the above does a synchronous callback of the change observer + mRequiresReload = false; + } + + @Override + public synchronized void getWords(final WordComposer codes, final WordCallback callback) { + if (mRequiresReload) loadDictionary(); + mInputLength = codes.size(); + mMaxDepth = mInputLength * 3; + getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1.0f, 0, callback); + } + + @Override + public synchronized boolean isValidWord(CharSequence word) { + if (mRequiresReload) loadDictionary(); + return isValidWordRec(mRoots, word, 0, word.length()); + } + + private boolean isValidWordRec(final List children, final CharSequence word, + final int offset, final int length) { + final int count = children.size(); + char currentChar = word.charAt(offset); + for (int j = 0; j < count; j++) { + final Node node = children.get(j); + if (node.code == currentChar) { + if (offset == length - 1) { + if (node.terminal) { + return true; + } + } else { + if (node.children != null) { + if (isValidWordRec(node.children, word, offset + 1, length)) { + return true; + } + } + } + } + } + return false; + } + + static char toLowerCase(char c) { + if (c < BASE_CHARS.length) { + c = BASE_CHARS[c]; + } + c = Character.toLowerCase(c); + return c; + } + + /** + * Recursively traverse the tree for words that match the input. Input consists of + * a list of arrays. Each item in the list is one input character position. An input + * character is actually an array of multiple possible candidates. This function is not + * optimized for speed, assuming that the user dictionary will only be a few hundred words in + * size. + * @param roots node whose children have to be search for matches + * @param codes the input character codes + * @param word the word being composed as a possible match + * @param depth the depth of traversal - the length of the word being composed thus far + * @param completion whether the traversal is now in completion mode - meaning that we've + * exhausted the input and we're looking for all possible suffixes. + * @param snr current weight of the word being formed + * @param inputIndex position in the input characters. This can be off from the depth in + * case we skip over some punctuations such as apostrophe in the traversal. That is, if you type + * "wouldve", it could be matching "would've", so the depth will be one more than the + * inputIndex + * @param callback the callback class for adding a word + */ + private void getWordsRec(List roots, final WordComposer codes, final char[] word, + final int depth, boolean completion, float snr, int inputIndex, + WordCallback callback) { + final int count = roots.size(); + final int codeSize = mInputLength; + // Optimization: Prune out words that are too long compared to how much was typed. + if (depth > mMaxDepth) { + return; + } + int[] currentChars = null; + if (codeSize <= inputIndex) { + completion = true; + } else { + currentChars = codes.getCodesAt(inputIndex); + } + + for (int i = 0; i < count; i++) { + final Node node = roots.get(i); + final char c = node.code; + final char lowerC = toLowerCase(c); + boolean terminal = node.terminal; + List children = node.children; + int freq = node.frequency; + if (completion) { + word[depth] = c; + if (terminal) { + if (!callback.addWord(word, 0, depth + 1, (int) (freq * snr))) { + return; + } + } + if (children != null) { + getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, + callback); + } + } else if (c == QUOTE && currentChars[0] != QUOTE) { + // Skip the ' and continue deeper + word[depth] = QUOTE; + if (children != null) { + getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, + callback); + } + } else { + for (int j = 0; j < currentChars.length; j++) { + float addedAttenuation = (j > 0 ? 1f : 3f); + if (currentChars[j] == -1) { + break; + } + if (currentChars[j] == lowerC || currentChars[j] == c) { + word[depth] = c; + + if (codes.size() == depth + 1) { + if (terminal) { + if (INCLUDE_TYPED_WORD_IF_VALID + || !same(word, depth + 1, codes.getTypedWord())) { + callback.addWord(word, 0, depth + 1, + (int) (freq * snr * addedAttenuation + * FULL_WORD_FREQ_MULTIPLIER)); + } + } + if (children != null) { + getWordsRec(children, codes, word, depth + 1, + true, snr * addedAttenuation, inputIndex + 1, callback); + } + } else if (children != null) { + getWordsRec(children, codes, word, depth + 1, + false, snr * addedAttenuation, inputIndex + 1, callback); + } + } + } + } + } + } + + private void addWords(Cursor cursor) { + mRoots = new ArrayList(); + + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + String word = cursor.getString(INDEX_WORD); + int frequency = cursor.getInt(INDEX_FREQUENCY); + // Safeguard against adding really long words. Stack may overflow due + // to recursion + if (word.length() < MAX_WORD_LENGTH) { + addWordRec(mRoots, word, 0, frequency); + } + cursor.moveToNext(); + } + } + cursor.close(); + } + + private void addWordRec(List children, final String word, + final int depth, final int frequency) { + + final int wordLength = word.length(); + final char c = word.charAt(depth); + // Does children have the current character? + final int childrenLength = children.size(); + Node childNode = null; + boolean found = false; + for (int i = 0; i < childrenLength; i++) { + childNode = children.get(i); + if (childNode.code == c) { + found = true; + break; + } + } + if (!found) { + childNode = new Node(); + childNode.code = c; + children.add(childNode); + } + if (wordLength == depth + 1) { + // Terminate this word + childNode.terminal = true; + childNode.frequency += frequency; // If there are multiple similar words + return; + } + if (childNode.children == null) { + childNode.children = new ArrayList(); + } + addWordRec(childNode.children, word, depth + 1, frequency); + } + + /** + * Table mapping most combined Latin, Greek, and Cyrillic characters + * to their base characters. If c is in range, BASE_CHARS[c] == c + * if c is not a combined character, or the base character if it + * is combined. + */ + static final char BASE_CHARS[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0020, + 0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7, + 0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf, + 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00c6, 0x0043, + 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, + 0x00d0, 0x004e, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x00d7, + 0x004f, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00de, 0x0073, // Manually changed d8 to 4f + // Manually changed df to 73 + 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063, + 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069, + 0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7, + 0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079, // Manually changed f8 to 6f + 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063, + 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064, + 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065, + 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067, + 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127, + 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, + 0x0049, 0x0131, 0x0049, 0x0069, 0x004a, 0x006a, 0x004b, 0x006b, + 0x0138, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, + 0x006c, 0x0141, 0x0142, 0x004e, 0x006e, 0x004e, 0x006e, 0x004e, + 0x006e, 0x02bc, 0x014a, 0x014b, 0x004f, 0x006f, 0x004f, 0x006f, + 0x004f, 0x006f, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072, + 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073, + 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167, + 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, + 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079, + 0x0059, 0x005a, 0x007a, 0x005a, 0x007a, 0x005a, 0x007a, 0x0073, + 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187, + 0x0188, 0x0189, 0x018a, 0x018b, 0x018c, 0x018d, 0x018e, 0x018f, + 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197, + 0x0198, 0x0199, 0x019a, 0x019b, 0x019c, 0x019d, 0x019e, 0x019f, + 0x004f, 0x006f, 0x01a2, 0x01a3, 0x01a4, 0x01a5, 0x01a6, 0x01a7, + 0x01a8, 0x01a9, 0x01aa, 0x01ab, 0x01ac, 0x01ad, 0x01ae, 0x0055, + 0x0075, 0x01b1, 0x01b2, 0x01b3, 0x01b4, 0x01b5, 0x01b6, 0x01b7, + 0x01b8, 0x01b9, 0x01ba, 0x01bb, 0x01bc, 0x01bd, 0x01be, 0x01bf, + 0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0044, 0x0044, 0x0064, 0x004c, + 0x004c, 0x006c, 0x004e, 0x004e, 0x006e, 0x0041, 0x0061, 0x0049, + 0x0069, 0x004f, 0x006f, 0x0055, 0x0075, 0x00dc, 0x00fc, 0x00dc, + 0x00fc, 0x00dc, 0x00fc, 0x00dc, 0x00fc, 0x01dd, 0x00c4, 0x00e4, + 0x0226, 0x0227, 0x00c6, 0x00e6, 0x01e4, 0x01e5, 0x0047, 0x0067, + 0x004b, 0x006b, 0x004f, 0x006f, 0x01ea, 0x01eb, 0x01b7, 0x0292, + 0x006a, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01f6, 0x01f7, + 0x004e, 0x006e, 0x00c5, 0x00e5, 0x00c6, 0x00e6, 0x00d8, 0x00f8, + 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065, + 0x0049, 0x0069, 0x0049, 0x0069, 0x004f, 0x006f, 0x004f, 0x006f, + 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075, + 0x0053, 0x0073, 0x0054, 0x0074, 0x021c, 0x021d, 0x0048, 0x0068, + 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061, + 0x0045, 0x0065, 0x00d6, 0x00f6, 0x00d5, 0x00f5, 0x004f, 0x006f, + 0x022e, 0x022f, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237, + 0x0238, 0x0239, 0x023a, 0x023b, 0x023c, 0x023d, 0x023e, 0x023f, + 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247, + 0x0248, 0x0249, 0x024a, 0x024b, 0x024c, 0x024d, 0x024e, 0x024f, + 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257, + 0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f, + 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267, + 0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f, + 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277, + 0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f, + 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287, + 0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f, + 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, + 0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f, + 0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7, + 0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af, + 0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077, + 0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf, + 0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7, + 0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf, + 0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7, + 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df, + 0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7, + 0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef, + 0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7, + 0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff, + 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, + 0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f, + 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, + 0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f, + 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, + 0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f, + 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, + 0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f, + 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347, + 0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f, + 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, + 0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f, + 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, + 0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f, + 0x0370, 0x0371, 0x0372, 0x0373, 0x02b9, 0x0375, 0x0376, 0x0377, + 0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f, + 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00a8, 0x0391, 0x00b7, + 0x0395, 0x0397, 0x0399, 0x038b, 0x039f, 0x038d, 0x03a5, 0x03a9, + 0x03ca, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, + 0x03a8, 0x03a9, 0x0399, 0x03a5, 0x03b1, 0x03b5, 0x03b7, 0x03b9, + 0x03cb, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, + 0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03cf, + 0x03b2, 0x03b8, 0x03a5, 0x03d2, 0x03d2, 0x03c6, 0x03c0, 0x03d7, + 0x03d8, 0x03d9, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df, + 0x03e0, 0x03e1, 0x03e2, 0x03e3, 0x03e4, 0x03e5, 0x03e6, 0x03e7, + 0x03e8, 0x03e9, 0x03ea, 0x03eb, 0x03ec, 0x03ed, 0x03ee, 0x03ef, + 0x03ba, 0x03c1, 0x03c2, 0x03f3, 0x0398, 0x03b5, 0x03f6, 0x03f7, + 0x03f8, 0x03a3, 0x03fa, 0x03fb, 0x03fc, 0x03fd, 0x03fe, 0x03ff, + 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406, + 0x0408, 0x0409, 0x040a, 0x040b, 0x041a, 0x0418, 0x0423, 0x040f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0418, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, + 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456, + 0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f, + 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467, + 0x0468, 0x0469, 0x046a, 0x046b, 0x046c, 0x046d, 0x046e, 0x046f, + 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475, + 0x0478, 0x0479, 0x047a, 0x047b, 0x047c, 0x047d, 0x047e, 0x047f, + 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, + 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f, + 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, + 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x049f, + 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, + 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af, + 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, + 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x04be, 0x04bf, + 0x04c0, 0x0416, 0x0436, 0x04c3, 0x04c4, 0x04c5, 0x04c6, 0x04c7, + 0x04c8, 0x04c9, 0x04ca, 0x04cb, 0x04cc, 0x04cd, 0x04ce, 0x04cf, + 0x0410, 0x0430, 0x0410, 0x0430, 0x04d4, 0x04d5, 0x0415, 0x0435, + 0x04d8, 0x04d9, 0x04d8, 0x04d9, 0x0416, 0x0436, 0x0417, 0x0437, + 0x04e0, 0x04e1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041e, 0x043e, + 0x04e8, 0x04e9, 0x04e8, 0x04e9, 0x042d, 0x044d, 0x0423, 0x0443, + 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04f6, 0x04f7, + 0x042b, 0x044b, 0x04fa, 0x04fb, 0x04fc, 0x04fd, 0x04fe, 0x04ff, + }; + + // generated with: + // cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; $base[hex($foo[0])] = hex($foo[5]);} for ($i = 0; $i < 0x500; $i += 8) { for ($j = $i; $j < $i + 8; $j++) { printf("0x%04x, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }' + +} diff --git a/src/com/android/inputmethod/latin/WordComposer.java b/src/com/android/inputmethod/latin/WordComposer.java new file mode 100644 index 000000000..c950a7f18 --- /dev/null +++ b/src/com/android/inputmethod/latin/WordComposer.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import java.util.ArrayList; +import java.util.List; + +/** + * A place to store the currently composing word with information such as adjacent key codes as well + */ +public class WordComposer { + /** + * The list of unicode values for each keystroke (including surrounding keys) + */ + private List mCodes; + + /** + * The word chosen from the candidate list, until it is committed. + */ + private String mPreferredWord; + + private StringBuilder mTypedWord; + + /** + * Whether the user chose to capitalize the word. + */ + private boolean mIsCapitalized; + + WordComposer() { + mCodes = new ArrayList(12); + mTypedWord = new StringBuilder(20); + } + + /** + * Clear out the keys registered so far. + */ + public void reset() { + mCodes.clear(); + mIsCapitalized = false; + mPreferredWord = null; + mTypedWord.setLength(0); + } + + /** + * Number of keystrokes in the composing word. + * @return the number of keystrokes + */ + public int size() { + return mCodes.size(); + } + + /** + * Returns the codes at a particular position in the word. + * @param index the position in the word + * @return the unicode for the pressed and surrounding keys + */ + public int[] getCodesAt(int index) { + return mCodes.get(index); + } + + /** + * Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of + * the array containing unicode for adjacent keys, sorted by reducing probability/proximity. + * @param codes the array of unicode values + */ + public void add(int primaryCode, int[] codes) { + mTypedWord.append((char) primaryCode); + mCodes.add(codes); + } + + /** + * Delete the last keystroke as a result of hitting backspace. + */ + public void deleteLast() { + mCodes.remove(mCodes.size() - 1); + mTypedWord.deleteCharAt(mTypedWord.length() - 1); + } + + /** + * Returns the word as it was typed, without any correction applied. + * @return the word that was typed so far + */ + public CharSequence getTypedWord() { + int wordSize = mCodes.size(); + if (wordSize == 0) { + return null; + } +// StringBuffer sb = new StringBuffer(wordSize); +// for (int i = 0; i < wordSize; i++) { +// char c = (char) mCodes.get(i)[0]; +// if (i == 0 && mIsCapitalized) { +// c = Character.toUpperCase(c); +// } +// sb.append(c); +// } +// return sb; + return mTypedWord; + } + + public void setCapitalized(boolean capitalized) { + mIsCapitalized = capitalized; + } + + /** + * Whether or not the user typed a capital letter as the first letter in the word + * @return capitalization preference + */ + public boolean isCapitalized() { + return mIsCapitalized; + } + + /** + * Stores the user's selected word, before it is actually committed to the text field. + * @param preferred + */ + public void setPreferredWord(String preferred) { + mPreferredWord = preferred; + } + + /** + * Return the word chosen by the user, or the typed word if no other word was chosen. + * @return the preferred word + */ + public CharSequence getPreferredWord() { + return mPreferredWord != null ? mPreferredWord : getTypedWord(); + } +}