diff options
Diffstat (limited to 'java/src')
7 files changed, 219 insertions, 64 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index 07dae168f..5e73d6300 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -17,6 +17,7 @@ package com.android.inputmethod.keyboard; import com.android.inputmethod.latin.Utils; +import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; import java.util.Arrays; import java.util.Collections; @@ -59,6 +60,15 @@ public class ProximityInfo { return new ProximityInfo(1, 1, 1, 1, 1, Collections.<Key>emptyList()); } + public static ProximityInfo getSpellCheckerProximityInfo() { + final ProximityInfo spellCheckerProximityInfo = getDummyProximityInfo(); + spellCheckerProximityInfo.mNativeProximityInfo = + spellCheckerProximityInfo.setProximityInfoNative( + SpellCheckerProximityInfo.ROW_SIZE, + 480, 300, 10, 3, SpellCheckerProximityInfo.PROXIMITY); + return spellCheckerProximityInfo; + } + private int mNativeProximityInfo; static { Utils.loadNativeLibrary(); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 1c7599442..b8850680b 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -23,7 +23,6 @@ import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; -import android.util.Log; import java.io.File; import java.io.FileInputStream; @@ -53,38 +52,55 @@ public class BinaryDictionaryFileDumper { } /** - * Generates a file name that matches the locale passed as an argument. - * The file name is basically the result of the .toString() method, except we replace - * any @File.separator with an underscore to avoid generating a file name that may not - * be created. - * @param locale the locale for which to get the file name - * @param context the context to use for getting the directory - * @return the name of the file to be created + * Escapes a string for any characters that may be suspicious for a file or directory name. + * + * Concretely this does a sort of URL-encoding except it will encode everything that's not + * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which + * we cannot allow here) */ - private static String getCacheFileNameForLocale(Locale locale, Context context) { - // The following assumes two things : - // 1. That File.separator is not the same character as "_" - // I don't think any android system will ever use "_" as a path separator - // 2. That no two locales differ by only a File.separator versus a "_" - // Since "_" can't be part of locale components this should be safe. - // Examples: - // en -> en - // en_US_POSIX -> en_US_POSIX - // en__foo/bar -> en__foo_bar - final String[] separator = { File.separator }; - final String[] empty = { "_" }; - final CharSequence basename = TextUtils.replace(locale.toString(), separator, empty); - return context.getFilesDir() + File.separator + basename; + // TODO: create a unit test for this method + private static String replaceFileNameDangerousCharacters(String name) { + // This assumes '%' is fully available as a non-separator, normal + // character in a file name. This is probably true for all file systems. + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < name.length(); ++i) { + final int codePoint = name.codePointAt(i); + if (Character.isLetterOrDigit(codePoint) || '_' == codePoint) { + sb.appendCodePoint(codePoint); + } else { + sb.append('%'); + sb.append(Integer.toHexString(codePoint)); + } + } + return sb.toString(); } /** - * Return for a given locale the provider URI to query to get the dictionary. + * Find out the cache directory associated with a specific locale. */ - // TODO: remove this - public static Uri getProviderUri(Locale locale) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath( - locale.toString()).build(); + private static String getCacheDirectoryForLocale(Locale locale, Context context) { + final String directoryName = replaceFileNameDangerousCharacters(locale.toString()); + return context.getFilesDir() + File.separator + directoryName; + } + + /** + * Generates a file name for the id and locale passed as an argument. + * + * In the current implementation the file name returned will always be unique for + * any id/locale pair, but please do not expect that the id can be the same for + * different dictionaries with different locales. An id should be unique for any + * dictionary. + * The file name is pretty much an URL-encoded version of the id inside a directory + * named like the locale, except it will also escape characters that look dangerous + * to some file systems. + * @param id the id of the dictionary for which to get a file name + * @param locale the locale for which to get the file name + * @param context the context to use for getting the directory + * @return the name of the file to be created + */ + private static String getCacheFileName(String id, Locale locale, Context context) { + final String fileName = replaceFileNameDangerousCharacters(id); + return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; } /** @@ -102,7 +118,7 @@ public class BinaryDictionaryFileDumper { */ private static List<String> getDictIdList(final Locale locale, final Context context) { final ContentResolver resolver = context.getContentResolver(); - final Uri dictionaryPackUri = getProviderUri(locale); + final Uri dictionaryPackUri = getProviderUri(locale.toString()); final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null, null); if (null == c) return Collections.<String>emptyList(); @@ -137,10 +153,6 @@ public class BinaryDictionaryFileDumper { */ public static List<AssetFileAddress> getDictSetFromContentProvider(final Locale locale, final Context context) throws FileNotFoundException, IOException { - // TODO: check whether the dictionary is the same or not and if it is, return the cached - // file. - // TODO: This should be able to read a number of files from the dictionary pack, copy - // them all and return them. final ContentResolver resolver = context.getContentResolver(); final List<String> idList = getDictIdList(locale, context); final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>(); @@ -149,8 +161,8 @@ public class BinaryDictionaryFileDumper { final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(dictionaryPackUri, "r"); if (null == afd) continue; - final String fileName = - copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context)); + final String fileName = copyFileTo(afd.createInputStream(), + getCacheFileName(id, locale, context)); afd.close(); fileAddressList.add(AssetFileAddress.makeFromFileName(fileName)); } @@ -158,18 +170,6 @@ public class BinaryDictionaryFileDumper { } /** - * Accepts a file as dictionary data for some locale and returns the name of a file. - * - * This will make the data in the input file the cached dictionary for this locale, overwriting - * any previous cached data. - */ - public static String getDictionaryFileFromFile(String fileName, Locale locale, - Context context) throws FileNotFoundException, IOException { - return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale, - context)); - } - - /** * Accepts a resource number as dictionary data for some locale and returns the name of a file. * * This will make the resource the cached dictionary for this locale, overwriting any previous @@ -181,7 +181,7 @@ public class BinaryDictionaryFileDumper { final Locale savedLocale = Utils.setSystemLocale(res, locale); final InputStream stream = res.openRawResource(resource); Utils.setSystemLocale(res, savedLocale); - return copyFileTo(stream, getCacheFileNameForLocale(locale, context)); + return copyFileTo(stream, getCacheFileName(Integer.toString(resource), locale, context)); } /** diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java index 66a041508..8a7dfb839 100644 --- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java @@ -49,20 +49,28 @@ public class ContactsDictionary extends ExpandableDictionary { private long mLastLoadedContacts; - public ContactsDictionary(Context context, int dicTypeId) { + public ContactsDictionary(final Context context, final int dicTypeId) { super(context, dicTypeId); + registerObserver(context); + loadDictionary(); + } + + private synchronized void registerObserver(final Context context) { // Perform a managed query. The Activity will handle closing and requerying the cursor // when needed. + if (mObserver != null) return; ContentResolver cres = context.getContentResolver(); - cres.registerContentObserver( - Contacts.CONTENT_URI, true,mObserver = new ContentObserver(null) { + Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) { @Override public void onChange(boolean self) { setRequiresReload(true); } }); - loadDictionary(); + } + + public void reopen(final Context context) { + registerObserver(context); } @Override diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index a58815892..d74babf4f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -509,7 +509,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this); if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr()); - resetContactsDictionary(); + resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); } private void initSuggest() { @@ -518,8 +518,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar final Resources res = mResources; final Locale savedLocale = Utils.setSystemLocale(res, keyboardLocale); + final ContactsDictionary oldContactsDictionary; if (mSuggest != null) { + oldContactsDictionary = mSuggest.getContactsDictionary(); mSuggest.close(); + } else { + oldContactsDictionary = null; } int mainDicResId = Utils.getMainDictionaryResourceId(res); @@ -533,7 +537,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mSuggest.setUserDictionary(mUserDictionary); mIsUserDictionaryAvaliable = mUserDictionary.isEnabled(); - resetContactsDictionary(); + resetContactsDictionary(oldContactsDictionary); mUserUnigramDictionary = new UserUnigramDictionary(this, this, localeStr, Suggest.DIC_USER_UNIGRAM); @@ -548,11 +552,36 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar Utils.setSystemLocale(res, savedLocale); } - private void resetContactsDictionary() { - if (null == mSuggest) return; - ContactsDictionary contactsDictionary = mSettingsValues.mUseContactsDict - ? new ContactsDictionary(this, Suggest.DIC_CONTACTS) : null; - mSuggest.setContactsDictionary(contactsDictionary); + /** + * Resets the contacts dictionary in mSuggest according to the user settings. + * + * This method takes an optional contacts dictionary to use. Since the contacts dictionary + * does not depend on the locale, it can be reused across different instances of Suggest. + * The dictionary will also be opened or closed as necessary depending on the settings. + * + * @param oldContactsDictionary an optional dictionary to use, or null + */ + private void resetContactsDictionary(final ContactsDictionary oldContactsDictionary) { + final boolean shouldSetDictionary = (null != mSuggest && mSettingsValues.mUseContactsDict); + + final ContactsDictionary dictionaryToUse; + if (!shouldSetDictionary) { + // Make sure the dictionary is closed. If it is already closed, this is a no-op, + // so it's safe to call it anyways. + if (null != oldContactsDictionary) oldContactsDictionary.close(); + dictionaryToUse = null; + } else if (null != oldContactsDictionary) { + // Make sure the old contacts dictionary is opened. If it is already open, this is a + // no-op, so it's safe to call it anyways. + oldContactsDictionary.reopen(this); + dictionaryToUse = oldContactsDictionary; + } else { + dictionaryToUse = new ContactsDictionary(this, Suggest.DIC_CONTACTS); + } + + if (null != mSuggest) { + mSuggest.setContactsDictionary(dictionaryToUse); + } } /* package private */ void resetSuggestMainDict() { diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 36a29e896..a2d66f398 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -88,6 +88,7 @@ public class Suggest implements Dictionary.WordCallback { private AutoCorrection mAutoCorrection; private Dictionary mMainDict; + private ContactsDictionary mContactsDict; private WhitelistDictionary mWhiteListDictionary; private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>(); private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>(); @@ -197,6 +198,10 @@ public class Suggest implements Dictionary.WordCallback { return mMainDict != null; } + public ContactsDictionary getContactsDictionary() { + return mContactsDict; + } + public Map<String, Dictionary> getUnigramDictionaries() { return mUnigramDictionaries; } @@ -218,7 +223,8 @@ public class Suggest implements Dictionary.WordCallback { * the contacts dictionary by passing null to this method. In this case no contacts dictionary * won't be used. */ - public void setContactsDictionary(Dictionary contactsDictionary) { + public void setContactsDictionary(ContactsDictionary contactsDictionary) { + mContactsDict = contactsDictionary; addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary); addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary); } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 7c92bc82a..44e999572 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin.spellcheck; import android.content.res.Resources; import android.service.textservice.SpellCheckerService; import android.service.textservice.SpellCheckerService.Session; -import android.util.Log; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; @@ -33,6 +32,7 @@ import com.android.inputmethod.latin.DictionaryFactory; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.WordComposer; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.LinkedList; @@ -48,7 +48,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService { private static final boolean DBG = true; private final static String[] emptyArray = new String[0]; - private final ProximityInfo mProximityInfo = ProximityInfo.getDummyProximityInfo(); + private final ProximityInfo mProximityInfo = ProximityInfo.getSpellCheckerProximityInfo(); private final Map<String, Dictionary> mDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>()); @@ -141,8 +141,16 @@ public class AndroidSpellCheckerService extends SpellCheckerService { final WordComposer composer = new WordComposer(); final int length = text.length(); for (int i = 0; i < length; ++i) { - int character = text.codePointAt(i); - composer.add(character, new int[] { character }, + final int character = text.codePointAt(i); + final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character); + final int[] proximities; + if (-1 == proximityIndex) { + proximities = new int[] { character }; + } else { + proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY, + proximityIndex, proximityIndex + SpellCheckerProximityInfo.ROW_SIZE); + } + composer.add(character, proximities, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); } dictionary.getWords(composer, suggestionsGatherer, mProximityInfo); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java new file mode 100644 index 000000000..abcf7e52a --- /dev/null +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2011 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.spellcheck; + +import com.android.inputmethod.keyboard.KeyDetector; +import com.android.inputmethod.keyboard.ProximityInfo; + +import java.util.Map; +import java.util.TreeMap; + +public class SpellCheckerProximityInfo { + final private static int NUL = KeyDetector.NOT_A_CODE; + + // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside + // native code - this value is passed at creation of the binary object and reused + // as the size of the passed array afterwards so they can't be different. + final public static int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; + + // This is a map from the code point to the index in the PROXIMITY array. + // At the time the native code to read the binary dictionary needs the proximity info be passed + // as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input character. + // Since we need to build such an array, we want to be able to search in our big proximity data + // quickly by character, and a map is probably the best way to do this. + final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>(); + + // The proximity here is the union of + // - the proximity for a QWERTY keyboard. + // - the proximity for an AZERTY keyboard. + // - the proximity for a QWERTZ keyboard. + // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other. + // + // The reasoning behind this construction is, almost any alphabetic text we may want + // to spell check has been entered with one of the keyboards above. Also, specifically + // to English, many spelling errors consist of the last vowel of the word being wrong + // because in English vowels tend to merge with each other in pronunciation. + final public static int[] PROXIMITY = { + 'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, + 'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL, + 'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + 'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, + 's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, + 'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + + 'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL, + 'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + 'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, + }; + static { + for (int i = 0; i < PROXIMITY.length; i += ROW_SIZE) { + if (NUL != PROXIMITY[i]) INDICES.put(PROXIMITY[i], i); + } + } + public static int getIndexOf(int characterCode) { + final Integer result = INDICES.get(characterCode); + if (null == result) return -1; + return result; + } +} |