aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java3
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java22
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java113
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java393
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java49
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsValues.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java14
-rw-r--r--java/src/com/android/inputmethod/latin/WhitelistDictionary.java1
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java4
9 files changed, 483 insertions, 118 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index cc7540e4e..2d958e17d 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -17,11 +17,8 @@
package com.android.inputmethod.latin;
import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import java.util.Arrays;
import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index e4d839690..b0c2adc79 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -20,11 +20,8 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
import android.util.Log;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-
import java.io.File;
import java.util.ArrayList;
import java.util.Locale;
@@ -155,14 +152,8 @@ class BinaryDictionaryGetter {
* Returns a file address from a resource, or null if it cannot be opened.
*/
private static AssetFileAddress loadFallbackResource(final Context context,
- final int fallbackResId, final Locale locale) {
- final RunInLocale<AssetFileDescriptor> job = new RunInLocale<AssetFileDescriptor>() {
- @Override
- protected AssetFileDescriptor job(Resources res) {
- return res.openRawResourceFd(fallbackResId);
- }
- };
- final AssetFileDescriptor afd = job.runInLocale(context.getResources(), locale);
+ final int fallbackResId) {
+ final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId);
if (afd == null) {
Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId="
+ fallbackResId);
@@ -264,13 +255,13 @@ class BinaryDictionaryGetter {
* - Uses a content provider to get a public dictionary set, as per the protocol described
* in BinaryDictionaryFileDumper.
* If that fails:
- * - Gets a file name from the fallback resource passed as an argument.
+ * - Gets a file name from the built-in dictionary for this locale, if any.
* If that fails:
* - Returns null.
* @return The list of addresses of valid dictionary files, or null.
*/
public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale,
- final Context context, final int fallbackResId) {
+ final Context context) {
// cacheWordListsFromContentProvider returns the list of files it copied to local
// storage, but we don't really care about what was copied NOW: what we want is the
@@ -299,8 +290,9 @@ class BinaryDictionaryGetter {
}
if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) {
- final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId,
- locale);
+ final int fallbackResId =
+ DictionaryFactory.getMainDictionaryResourceId(context.getResources(), locale);
+ final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId);
if (null != fallbackAsset) {
fileList.add(fallbackAsset);
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index fedb45407..bf05f3bc3 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -21,8 +21,6 @@ import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.util.Log;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -38,24 +36,22 @@ public class DictionaryFactory {
* Initializes a dictionary from a dictionary pack, with explicit flags.
*
* This searches for a content provider providing a dictionary pack for the specified
- * locale. If none is found, it falls back to using the resource passed as fallBackResId
- * as a dictionary.
+ * locale. If none is found, it falls back to the built-in dictionary - if any.
* @param context application context for reading resources
* @param locale the locale for which to create the dictionary
- * @param fallbackResId the id of the resource to use as a fallback if no pack is found
* @param useFullEditDistance whether to use the full edit distance in suggestions
* @return an initialized instance of DictionaryCollection
*/
public static DictionaryCollection createDictionaryFromManager(final Context context,
- final Locale locale, final int fallbackResId, final boolean useFullEditDistance) {
+ final Locale locale, final boolean useFullEditDistance) {
if (null == locale) {
Log.e(TAG, "No locale defined for dictionary");
- return new DictionaryCollection(createBinaryDictionary(context, fallbackResId, locale));
+ return new DictionaryCollection(createBinaryDictionary(context, locale));
}
final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
final ArrayList<AssetFileAddress> assetFileList =
- BinaryDictionaryGetter.getDictionaryFiles(locale, context, fallbackResId);
+ BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
for (final AssetFileAddress f : assetFileList) {
final BinaryDictionary binaryDictionary =
@@ -77,17 +73,14 @@ public class DictionaryFactory {
* Initializes a dictionary from a dictionary pack, with default flags.
*
* This searches for a content provider providing a dictionary pack for the specified
- * locale. If none is found, it falls back to using the resource passed as fallBackResId
- * as a dictionary.
+ * locale. If none is found, it falls back to the built-in dictionary, if any.
* @param context application context for reading resources
* @param locale the locale for which to create the dictionary
- * @param fallbackResId the id of the resource to use as a fallback if no pack is found
* @return an initialized instance of DictionaryCollection
*/
public static DictionaryCollection createDictionaryFromManager(final Context context,
- final Locale locale, final int fallbackResId) {
- return createDictionaryFromManager(context, locale, fallbackResId,
- false /* useFullEditDistance */);
+ final Locale locale) {
+ return createDictionaryFromManager(context, locale, false /* useFullEditDistance */);
}
/**
@@ -98,16 +91,11 @@ public class DictionaryFactory {
* @return an initialized instance of BinaryDictionary
*/
protected static BinaryDictionary createBinaryDictionary(final Context context,
- final int resId, final Locale locale) {
+ final Locale locale) {
AssetFileDescriptor afd = null;
try {
- final RunInLocale<AssetFileDescriptor> job = new RunInLocale<AssetFileDescriptor>() {
- @Override
- protected AssetFileDescriptor job(Resources res) {
- return res.openRawResourceFd(resId);
- }
- };
- afd = job.runInLocale(context.getResources(), locale);
+ final int resId = getMainDictionaryResourceId(context.getResources(), locale);
+ afd = context.getResources().openRawResourceFd(resId);
if (afd == null) {
Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
return null;
@@ -123,7 +111,7 @@ public class DictionaryFactory {
return new BinaryDictionary(context, sourceDir, afd.getStartOffset(), afd.getLength(),
false /* useFullEditDistance */, locale);
} catch (android.content.res.Resources.NotFoundException e) {
- Log.e(TAG, "Could not find the resource. resId=" + resId);
+ Log.e(TAG, "Could not find the resource");
return null;
} finally {
if (null != afd) {
@@ -163,41 +151,31 @@ public class DictionaryFactory {
* @return whether a (non-placeholder) dictionary is available or not.
*/
public static boolean isDictionaryAvailable(Context context, Locale locale) {
- final RunInLocale<Boolean> job = new RunInLocale<Boolean>() {
- @Override
- protected Boolean job(Resources res) {
- final int resourceId = getMainDictionaryResourceId(res);
- final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
- final boolean hasDictionary = isFullDictionary(afd);
- try {
- if (null != afd) afd.close();
- } catch (java.io.IOException e) {
- /* Um, what can we do here exactly? */
- }
- return hasDictionary;
- }
- };
- return job.runInLocale(context.getResources(), locale);
+ final Resources res = context.getResources();
+ final int resourceId = getMainDictionaryResourceId(res, locale);
+ final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
+ final boolean hasDictionary = isFullDictionary(afd);
+ try {
+ if (null != afd) afd.close();
+ } catch (java.io.IOException e) {
+ /* Um, what can we do here exactly? */
+ }
+ return hasDictionary;
}
// TODO: Do not use the size of the dictionary as an unique dictionary ID.
public static Long getDictionaryId(final Context context, final Locale locale) {
- final RunInLocale<Long> job = new RunInLocale<Long>() {
- @Override
- protected Long job(Resources res) {
- final int resourceId = getMainDictionaryResourceId(res);
- final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
- final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
- ? afd.getLength()
- : null;
- try {
- if (null != afd) afd.close();
- } catch (java.io.IOException e) {
- }
- return size;
- }
- };
- return job.runInLocale(context.getResources(), locale);
+ final Resources res = context.getResources();
+ final int resourceId = getMainDictionaryResourceId(res, locale);
+ final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
+ final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
+ ? afd.getLength()
+ : null;
+ try {
+ if (null != afd) afd.close();
+ } catch (java.io.IOException e) {
+ }
+ return size;
}
// TODO: Find the Right Way to find out whether the resource is a placeholder or not.
@@ -214,13 +192,32 @@ public class DictionaryFactory {
return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
}
+ private static final String DEFAULT_MAIN_DICT = "main";
+ private static final String MAIN_DICT_PREFIX = "main_";
+
/**
* Returns a main dictionary resource id
+ * @param locale dictionary locale
* @return main dictionary resource id
*/
- public static int getMainDictionaryResourceId(Resources res) {
- final String MAIN_DIC_NAME = "main";
- String packageName = LatinIME.class.getPackage().getName();
- return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
+ public static int getMainDictionaryResourceId(Resources res, Locale locale) {
+ final String packageName = LatinIME.class.getPackage().getName();
+ int resId;
+
+ // Try to find main_language_country dictionary.
+ if (!locale.getCountry().isEmpty()) {
+ final String dictLanguageCountry = MAIN_DICT_PREFIX + locale.toString().toLowerCase();
+ if ((resId = res.getIdentifier(dictLanguageCountry, "raw", packageName)) != 0) {
+ return resId;
+ }
+ }
+
+ // Try to find main_language dictionary.
+ final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage();
+ if ((resId = res.getIdentifier(dictLanguage, "raw", packageName)) != 0) {
+ return resId;
+ }
+
+ return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", packageName);
}
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
new file mode 100644
index 000000000..53e8b74de
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2012 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 android.content.Context;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Abstract base class for an expandable dictionary that can be created and updated dynamically
+ * during runtime. When updated it automatically generates a new binary dictionary to handle future
+ * queries in native code. This binary dictionary is written to internal storage, and potentially
+ * shared across multiple ExpandableBinaryDictionary instances. Updates to each dictionary filename
+ * are controlled across multiple instances to ensure that only one instance can update the same
+ * dictionary at the same time.
+ */
+abstract public class ExpandableBinaryDictionary extends Dictionary {
+
+ /** Used for Log actions from this class */
+ private static final String TAG = ExpandableBinaryDictionary.class.getSimpleName();
+
+ /** Whether to print debug output to log */
+ private static boolean DEBUG = false;
+
+ /**
+ * The maximum length of a word in this dictionary. This is the same value as the binary
+ * dictionary.
+ */
+ protected static final int MAX_WORD_LENGTH = BinaryDictionary.MAX_WORD_LENGTH;
+
+ /**
+ * A static map of locks, each of which controls access to a single binary dictionary file. They
+ * ensure that only one instance can update the same dictionary at the same time. The key for
+ * this map is the filename and the value is the shared dictionary controller associated with
+ * that filename.
+ */
+ private static final HashMap<String, DictionaryController> sSharedDictionaryControllers =
+ new HashMap<String, DictionaryController>();
+
+ /** The application context. */
+ protected final Context mContext;
+
+ /**
+ * The binary dictionary generated dynamically from the fusion dictionary. This is used to
+ * answer unigram and bigram queries.
+ */
+ private BinaryDictionary mBinaryDictionary;
+
+ /** The expandable fusion dictionary used to generate the binary dictionary. */
+ private FusionDictionary mFusionDictionary;
+
+ /** The dictionary type id. */
+ public final int mDicTypeId;
+
+ /**
+ * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
+ * dictionary instances with the same filename is supported, with access controlled by
+ * DictionaryController.
+ */
+ private final String mFilename;
+
+ /** Controls access to the shared binary dictionary file across multiple instances. */
+ private final DictionaryController mSharedDictionaryController;
+
+ /** Controls access to the local binary dictionary for this instance. */
+ private final DictionaryController mLocalDictionaryController = new DictionaryController();
+
+ /**
+ * Abstract method for loading the unigrams and bigrams of a given dictionary in a background
+ * thread.
+ */
+ protected abstract void loadDictionaryAsync();
+
+ /**
+ * Gets the shared dictionary controller for the given filename.
+ */
+ private static synchronized DictionaryController getSharedDictionaryController(
+ String filename) {
+ DictionaryController controller = sSharedDictionaryControllers.get(filename);
+ if (controller == null) {
+ controller = new DictionaryController();
+ sSharedDictionaryControllers.put(filename, controller);
+ }
+ return controller;
+ }
+
+ /**
+ * Creates a new expandable binary dictionary.
+ *
+ * @param context The application context of the parent.
+ * @param filename The filename for this binary dictionary. Multiple dictionaries with the same
+ * filename is supported.
+ * @param dictType The type of this dictionary.
+ */
+ public ExpandableBinaryDictionary(
+ final Context context, final String filename, final int dictType) {
+ mDicTypeId = dictType;
+ mFilename = filename;
+ mContext = context;
+ mBinaryDictionary = null;
+ mSharedDictionaryController = getSharedDictionaryController(filename);
+ clearFusionDictionary();
+ }
+
+ /**
+ * Closes and cleans up the binary dictionary.
+ */
+ @Override
+ public void close() {
+ // Ensure that no other threads are accessing the local binary dictionary.
+ mLocalDictionaryController.lock();
+ try {
+ if (mBinaryDictionary != null) {
+ mBinaryDictionary.close();
+ mBinaryDictionary = null;
+ }
+ } finally {
+ mLocalDictionaryController.unlock();
+ }
+ }
+
+ /**
+ * Clears the fusion dictionary on the Java side. Note: Does not modify the binary dictionary on
+ * the native side.
+ */
+ public void clearFusionDictionary() {
+ mFusionDictionary = new FusionDictionary(new Node(), new FusionDictionary.DictionaryOptions(
+ new HashMap<String, String>(), false, false));
+ }
+
+ /**
+ * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes
+ * are done to update the binary dictionary.
+ */
+ // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
+ // considering performance regression.
+ protected void addWord(final String word, final int frequency) {
+ mFusionDictionary.add(word, frequency, null, null);
+ }
+
+ /**
+ * Sets a word bigram in the fusion dictionary. Call updateBinaryDictionary when all changes are
+ * done to update the binary dictionary.
+ */
+ // TODO: Create "cache dictionary" to cache fresh bigrams for frequently updated dictionaries,
+ // considering performance regression.
+ protected void setBigram(final String prevWord, final String word, final int frequency) {
+ mFusionDictionary.setBigram(prevWord, word, frequency);
+ }
+
+ @Override
+ public void getWords(final WordComposer codes, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
+ asyncReloadDictionaryIfRequired();
+ getWordsInner(codes, callback, proximityInfo);
+ }
+
+ protected final void getWordsInner(final WordComposer codes, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
+ // Ensure that there are no concurrent calls to getWords. If there are, do nothing and
+ // return.
+ if (mLocalDictionaryController.tryLock()) {
+ try {
+ if (mBinaryDictionary != null) {
+ mBinaryDictionary.getWords(codes, callback, proximityInfo);
+ }
+ } finally {
+ mLocalDictionaryController.unlock();
+ }
+ }
+ }
+
+ @Override
+ public void getBigrams(final WordComposer codes, final CharSequence previousWord,
+ final WordCallback callback) {
+ asyncReloadDictionaryIfRequired();
+ getBigramsInner(codes, previousWord, callback);
+ }
+
+ protected void getBigramsInner(final WordComposer codes, final CharSequence previousWord,
+ final WordCallback callback) {
+ if (mLocalDictionaryController.tryLock()) {
+ try {
+ if (mBinaryDictionary != null) {
+ mBinaryDictionary.getBigrams(codes, previousWord, callback);
+ }
+ } finally {
+ mLocalDictionaryController.unlock();
+ }
+ }
+ }
+
+ @Override
+ public boolean isValidWord(final CharSequence word) {
+ asyncReloadDictionaryIfRequired();
+ return isValidWordInner(word);
+ }
+
+ protected boolean isValidWordInner(final CharSequence word) {
+ if (mLocalDictionaryController.tryLock()) {
+ try {
+ if (mBinaryDictionary != null) {
+ return mBinaryDictionary.isValidWord(word);
+ }
+ } finally {
+ mLocalDictionaryController.unlock();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Load the current binary dictionary from internal storage in a background thread. If no binary
+ * dictionary exists, this method will generate one.
+ */
+ protected void loadDictionary() {
+ mLocalDictionaryController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
+ asyncReloadDictionaryIfRequired();
+ }
+
+ /**
+ * Loads the current binary dictionary from internal storage. Assumes the dictionary file
+ * exists.
+ */
+ protected void loadBinaryDictionary() {
+ if (DEBUG) {
+ Log.d(TAG, "Loading binary dictionary: request="
+ + mSharedDictionaryController.mLastUpdateRequestTime + " update="
+ + mSharedDictionaryController.mLastUpdateTime);
+ }
+
+ final File file = new File(mContext.getFilesDir(), mFilename);
+ final String filename = file.getAbsolutePath();
+ final long length = file.length();
+
+ // Build the new binary dictionary
+ final BinaryDictionary newBinaryDictionary =
+ new BinaryDictionary(mContext, filename, 0, length, true /* useFullEditDistance */,
+ null);
+
+ if (mBinaryDictionary != null) {
+ // Ensure all threads accessing the current dictionary have finished before swapping in
+ // the new one.
+ final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
+ mLocalDictionaryController.lock();
+ mBinaryDictionary = newBinaryDictionary;
+ mLocalDictionaryController.unlock();
+ oldBinaryDictionary.close();
+ } else {
+ mBinaryDictionary = newBinaryDictionary;
+ }
+ }
+
+ /**
+ * Generates and writes a new binary dictionary based on the contents of the fusion dictionary.
+ */
+ private void generateBinaryDictionary() {
+ if (DEBUG) {
+ Log.d(TAG, "Generating binary dictionary: request="
+ + mSharedDictionaryController.mLastUpdateRequestTime + " update="
+ + mSharedDictionaryController.mLastUpdateTime);
+ }
+
+ loadDictionaryAsync();
+
+ final File file = new File(mContext.getFilesDir(), mFilename);
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(file);
+ BinaryDictInputOutput.writeDictionaryBinary(out, mFusionDictionary, 1);
+ out.flush();
+ out.close();
+ clearFusionDictionary();
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception while writing file: " + e);
+ } catch (UnsupportedFormatException e) {
+ Log.e(TAG, "Unsupported format: " + e);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets whether or not the dictionary is out of date and requires a reload.
+ */
+ protected void setRequiresReload(final boolean reload) {
+ final long time = reload ? SystemClock.uptimeMillis() : 0;
+ mSharedDictionaryController.mLastUpdateRequestTime = time;
+ mLocalDictionaryController.mLastUpdateRequestTime = time;
+ if (DEBUG) {
+ Log.d(TAG, "Reload request: request=" + time + " update="
+ + mSharedDictionaryController.mLastUpdateTime);
+ }
+ }
+
+ /**
+ * Reloads the dictionary if required. Reload will occur asynchronously in a separate thread.
+ */
+ void asyncReloadDictionaryIfRequired() {
+ new AsyncReloadDictionaryTask().start();
+ }
+
+ /**
+ * Reloads the dictionary if required. Access is controlled on a per dictionary file basis and
+ * supports concurrent calls from multiple instances that share the same dictionary file.
+ */
+ protected final void syncReloadDictionaryIfRequired() {
+ if (mBinaryDictionary != null && !mLocalDictionaryController.isOutOfDate()) {
+ return;
+ }
+
+ // Ensure that only one thread attempts to read or write to the shared binary dictionary
+ // file at the same time.
+ mSharedDictionaryController.lock();
+ try {
+ final long time = SystemClock.uptimeMillis();
+ if (mSharedDictionaryController.isOutOfDate() || !dictionaryFileExists()) {
+ // If the shared dictionary file does not exist or is out of date, the first
+ // instance that acquires the lock will generate a new one.
+ mSharedDictionaryController.mLastUpdateTime = time;
+ mLocalDictionaryController.mLastUpdateTime = time;
+ generateBinaryDictionary();
+ loadBinaryDictionary();
+ } else if (mLocalDictionaryController.isOutOfDate()) {
+ // Otherwise, if only the local dictionary for this instance is out of date, load
+ // the shared dictionary from file.
+ mLocalDictionaryController.mLastUpdateTime = time;
+ loadBinaryDictionary();
+ }
+ } finally {
+ mSharedDictionaryController.unlock();
+ }
+ }
+
+ private boolean dictionaryFileExists() {
+ final File file = new File(mContext.getFilesDir(), mFilename);
+ return file.exists();
+ }
+
+ /**
+ * Thread class for asynchronously reloading and rewriting the binary dictionary.
+ */
+ private class AsyncReloadDictionaryTask extends Thread {
+ @Override
+ public void run() {
+ syncReloadDictionaryIfRequired();
+ }
+ }
+
+ /**
+ * Lock for controlling access to a given binary dictionary and for tracking whether the
+ * dictionary is out of date. Can be shared across multiple dictionary instances that access the
+ * same filename.
+ */
+ private static class DictionaryController extends ReentrantLock {
+ private volatile long mLastUpdateTime = 0;
+ private volatile long mLastUpdateRequestTime = 0;
+
+ private boolean isOutOfDate() {
+ return (mLastUpdateRequestTime > mLastUpdateTime);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index e0fa2f838..7cdeef897 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -493,37 +493,28 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
final Locale keyboardLocale = mSubtypeSwitcher.getInputLocale();
- final Context context = this;
- final RunInLocale<Void> job = new RunInLocale<Void>() {
- @Override
- protected Void job(Resources res) {
- final ContactsDictionary oldContactsDictionary;
- if (mSuggest != null) {
- oldContactsDictionary = mSuggest.getContactsDictionary();
- mSuggest.close();
- } else {
- oldContactsDictionary = null;
- }
+ final ContactsDictionary oldContactsDictionary;
+ if (mSuggest != null) {
+ oldContactsDictionary = mSuggest.getContactsDictionary();
+ mSuggest.close();
+ } else {
+ oldContactsDictionary = null;
+ }
- int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(res);
- mSuggest = new Suggest(context, mainDicResId, keyboardLocale);
- if (mSettingsValues.mAutoCorrectEnabled) {
- mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
- }
+ mSuggest = new Suggest(this, keyboardLocale);
+ if (mSettingsValues.mAutoCorrectEnabled) {
+ mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
+ }
- mUserDictionary = new UserDictionary(context, localeStr);
- mSuggest.setUserDictionary(mUserDictionary);
- mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
+ mUserDictionary = new UserDictionary(this, localeStr);
+ mSuggest.setUserDictionary(mUserDictionary);
+ mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
- resetContactsDictionary(oldContactsDictionary);
+ resetContactsDictionary(oldContactsDictionary);
- mUserHistoryDictionary
- = new UserHistoryDictionary(context, localeStr, Suggest.DIC_USER_HISTORY);
- mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
- return null;
- }
- };
- job.runInLocale(mResources, keyboardLocale);
+ mUserHistoryDictionary = new UserHistoryDictionary(
+ this, localeStr, Suggest.DIC_USER_HISTORY);
+ mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
}
/**
@@ -559,9 +550,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
/* package private */ void resetSuggestMainDict() {
- final Locale keyboardLocale = mSubtypeSwitcher.getInputLocale();
- int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(mResources);
- mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
+ mSuggest.resetMainDict(this, mSubtypeSwitcher.getInputLocale());
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 49ab7f9d7..526acf128 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -97,7 +97,7 @@ public class SettingsValues {
}
}
final String[] suggestPuncsSpec = KeySpecParser.parseCsvString(
- res.getString(R.string.suggested_punctuations), res, R.string.english_ime_name);
+ res.getString(R.string.suggested_punctuations), null);
mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
mSymbolsExcludedFromWordSeparators =
res.getString(R.string.symbols_excluded_from_word_separators);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index fa6664b1a..c3f3bd598 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -104,8 +104,8 @@ public class Suggest implements Dictionary.WordCallback {
private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
- public Suggest(final Context context, final int dictionaryResId, final Locale locale) {
- initAsynchronously(context, dictionaryResId, locale);
+ public Suggest(final Context context, final Locale locale) {
+ initAsynchronously(context, locale);
}
/* package for test */ Suggest(final Context context, final File dictionary,
@@ -119,9 +119,8 @@ public class Suggest implements Dictionary.WordCallback {
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
}
- private void initAsynchronously(final Context context, final int dictionaryResId,
- final Locale locale) {
- resetMainDict(context, dictionaryResId, locale);
+ private void initAsynchronously(final Context context, final Locale locale) {
+ resetMainDict(context, locale);
// TODO: read the whitelist and init the pool asynchronously too.
// initPool should be done asynchronously now that the pool is thread-safe.
@@ -146,14 +145,13 @@ public class Suggest implements Dictionary.WordCallback {
}
}
- public void resetMainDict(final Context context, final int dictionaryResId,
- final Locale locale) {
+ public void resetMainDict(final Context context, final Locale locale) {
mMainDict = null;
new Thread("InitializeBinaryDictionary") {
@Override
public void run() {
final Dictionary newMainDict = DictionaryFactory.createDictionaryFromManager(
- context, locale, dictionaryResId);
+ context, locale);
mMainDict = newMainDict;
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict);
addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict);
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
index 7bb307662..bb3ba8651 100644
--- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
+++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
@@ -38,6 +38,7 @@ public class WhitelistDictionary extends ExpandableDictionary {
// TODO: Conform to the async load contact of ExpandableDictionary
public WhitelistDictionary(final Context context, final Locale locale) {
super(context, Suggest.DIC_WHITELIST);
+ // TODO: Move whitelist dictionary into main dictionary.
final RunInLocale<Void> job = new RunInLocale<Void>() {
@Override
protected Void job(Resources res) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 97296147f..576fbe696 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -386,10 +386,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService
final int script = getScriptFromLocale(locale);
final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo(
SpellCheckerProximityInfo.getProximityForScript(script));
- final Resources resources = getResources();
- final int fallbackResourceId = DictionaryFactory.getMainDictionaryResourceId(resources);
final DictionaryCollection dictionaryCollection =
- DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId,
+ DictionaryFactory.createDictionaryFromManager(this, locale,
true /* useFullEditDistance */);
final String localeStr = locale.toString();
Dictionary userDictionary = mUserDictionaries.get(localeStr);