aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/spellcheck
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin/spellcheck')
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java473
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java118
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java255
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java56
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java134
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java188
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java16
7 files changed, 588 insertions, 652 deletions
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 503b18b1b..90398deb2 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -16,40 +16,55 @@
package com.android.inputmethod.latin.spellcheck;
+import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.service.textservice.SpellCheckerService;
import android.text.InputType;
import android.util.Log;
+import android.util.LruCache;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.textservice.SuggestionsInfo;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.ContactsBinaryDictionary;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryCollection;
+import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
-import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.UserBinaryDictionary;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.ScriptUtils;
import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.SuggestionResults;
+import com.android.inputmethod.latin.WordComposer;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
/**
* Service for spell checking, using LatinIME's dictionaries and mechanisms.
@@ -58,61 +73,77 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
private static final boolean DBG = false;
- private static final int POOL_SIZE = 2;
public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480;
private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 368;
- private final static String[] EMPTY_STRING_ARRAY = new String[0];
- private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
- private Map<String, UserBinaryDictionary> mUserDictionaries =
- CollectionUtils.newSynchronizedTreeMap();
- private ContactsBinaryDictionary mContactsDictionary;
+ private static final String DICTIONARY_NAME_PREFIX = "spellcheck_";
+ private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
+ private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ private final HashSet<Locale> mCachedLocales = new HashSet<>();
+
+ private final int MAX_NUM_OF_THREADS_READ_DICTIONARY = 2;
+ private final Semaphore mSemaphore = new Semaphore(MAX_NUM_OF_THREADS_READ_DICTIONARY,
+ true /* fair */);
+ // TODO: Make each spell checker session has its own session id.
+ private final ConcurrentLinkedQueue<Integer> mSessionIdPool = new ConcurrentLinkedQueue<>();
+
+ private static class DictionaryFacilitatorLruCache extends
+ LruCache<Locale, DictionaryFacilitator> {
+ private final HashSet<Locale> mCachedLocales;
+ public DictionaryFacilitatorLruCache(final HashSet<Locale> cachedLocales, int maxSize) {
+ super(maxSize);
+ mCachedLocales = cachedLocales;
+ }
+
+ @Override
+ protected void entryRemoved(boolean evicted, Locale key,
+ DictionaryFacilitator oldValue, DictionaryFacilitator newValue) {
+ if (oldValue != null && oldValue != newValue) {
+ oldValue.closeDictionaries();
+ }
+ if (key != null && newValue == null) {
+ // Remove locale from the cache when the dictionary facilitator for the locale is
+ // evicted and new facilitator is not set for the locale.
+ mCachedLocales.remove(key);
+ if (size() >= maxSize()) {
+ Log.w(TAG, "DictionaryFacilitator for " + key.toString()
+ + " has been evicted due to cache size limit."
+ + " size: " + size() + ", maxSize: " + maxSize());
+ }
+ }
+ }
+ }
+
+ private static final int MAX_DICTIONARY_FACILITATOR_COUNT = 3;
+ private final LruCache<Locale, DictionaryFacilitator> mDictionaryFacilitatorCache =
+ new DictionaryFacilitatorLruCache(mCachedLocales, MAX_DICTIONARY_FACILITATOR_COUNT);
+ private final ConcurrentHashMap<Locale, Keyboard> mKeyboardCache = new ConcurrentHashMap<>();
// The threshold for a suggestion to be considered "recommended".
private float mRecommendedThreshold;
// Whether to use the contacts dictionary
private boolean mUseContactsDictionary;
- private final Object mUseContactsLock = new Object();
-
- private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
- CollectionUtils.newHashSet();
+ // TODO: make a spell checker option to block offensive words or not
+ private final SettingsValuesForSuggestion mSettingsValuesForSuggestion =
+ new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */,
+ true /* spaceAwareGestureEnabled */,
+ null /* additionalFeaturesSettingValues */);
+ private final Object mDictionaryLock = new Object();
- public static final int SCRIPT_LATIN = 0;
- public static final int SCRIPT_CYRILLIC = 1;
- public static final int SCRIPT_GREEK = 2;
public static final String SINGLE_QUOTE = "\u0027";
public static final String APOSTROPHE = "\u2019";
- private static final TreeMap<String, Integer> mLanguageToScript;
- static {
- // List of the supported languages and their associated script. We won't check
- // words written in another script than the selected script, because we know we
- // don't have those in our dictionary so we will underline everything and we
- // will never have any suggestions, so it makes no sense checking them, and this
- // is done in {@link #shouldFilterOut}. Also, the script is used to choose which
- // proximity to pass to the dictionary descent algorithm.
- // IMPORTANT: this only contains languages - do not write countries in there.
- // Only the language is searched from the map.
- mLanguageToScript = CollectionUtils.newTreeMap();
- mLanguageToScript.put("cs", SCRIPT_LATIN);
- mLanguageToScript.put("da", SCRIPT_LATIN);
- mLanguageToScript.put("de", SCRIPT_LATIN);
- mLanguageToScript.put("el", SCRIPT_GREEK);
- mLanguageToScript.put("en", SCRIPT_LATIN);
- mLanguageToScript.put("es", SCRIPT_LATIN);
- mLanguageToScript.put("fi", SCRIPT_LATIN);
- mLanguageToScript.put("fr", SCRIPT_LATIN);
- mLanguageToScript.put("hr", SCRIPT_LATIN);
- mLanguageToScript.put("it", SCRIPT_LATIN);
- mLanguageToScript.put("lt", SCRIPT_LATIN);
- mLanguageToScript.put("lv", SCRIPT_LATIN);
- mLanguageToScript.put("nb", SCRIPT_LATIN);
- mLanguageToScript.put("nl", SCRIPT_LATIN);
- mLanguageToScript.put("pt", SCRIPT_LATIN);
- mLanguageToScript.put("sl", SCRIPT_LATIN);
- mLanguageToScript.put("ru", SCRIPT_CYRILLIC);
+
+ public AndroidSpellCheckerService() {
+ super();
+ for (int i = 0; i < MAX_NUM_OF_THREADS_READ_DICTIONARY; i++) {
+ mSessionIdPool.add(i);
+ }
}
@Override public void onCreate() {
@@ -124,22 +155,17 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
}
- public static int getScriptFromLocale(final Locale locale) {
- final Integer script = mLanguageToScript.get(locale.getLanguage());
- if (null == script) {
- throw new RuntimeException("We have been called with an unsupported language: \""
- + locale.getLanguage() + "\". Framework bug?");
- }
- return script;
+ public float getRecommendedThreshold() {
+ return mRecommendedThreshold;
}
private static String getKeyboardLayoutNameForScript(final int script) {
switch (script) {
- case AndroidSpellCheckerService.SCRIPT_LATIN:
+ case ScriptUtils.SCRIPT_LATIN:
return "qwerty";
- case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+ case ScriptUtils.SCRIPT_CYRILLIC:
return "east_slavic";
- case AndroidSpellCheckerService.SCRIPT_GREEK:
+ case ScriptUtils.SCRIPT_GREEK:
return "greek";
default:
throw new RuntimeException("Wrong script supplied: " + script);
@@ -149,52 +175,21 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
@Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
- synchronized(mUseContactsLock) {
- mUseContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
- if (mUseContactsDictionary) {
- startUsingContactsDictionaryLocked();
- } else {
- stopUsingContactsDictionaryLocked();
- }
- }
- }
-
- private void startUsingContactsDictionaryLocked() {
- if (null == mContactsDictionary) {
- // TODO: use the right locale for each session
- mContactsDictionary =
- new SynchronouslyLoadedContactsBinaryDictionary(this, Locale.getDefault());
- }
- final Iterator<WeakReference<DictionaryCollection>> iterator =
- mDictionaryCollectionsList.iterator();
- while (iterator.hasNext()) {
- final WeakReference<DictionaryCollection> dictRef = iterator.next();
- final DictionaryCollection dict = dictRef.get();
- if (null == dict) {
- iterator.remove();
- } else {
- dict.addDictionary(mContactsDictionary);
- }
- }
- }
-
- private void stopUsingContactsDictionaryLocked() {
- if (null == mContactsDictionary) return;
- final Dictionary contactsDict = mContactsDictionary;
- // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY is no longer needed
- mContactsDictionary = null;
- final Iterator<WeakReference<DictionaryCollection>> iterator =
- mDictionaryCollectionsList.iterator();
- while (iterator.hasNext()) {
- final WeakReference<DictionaryCollection> dictRef = iterator.next();
- final DictionaryCollection dict = dictRef.get();
- if (null == dict) {
- iterator.remove();
- } else {
- dict.removeDictionary(contactsDict);
+ final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
+ if (useContactsDictionary != mUseContactsDictionary) {
+ mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
+ try {
+ mUseContactsDictionary = useContactsDictionary;
+ for (final Locale locale : mCachedLocales) {
+ final DictionaryFacilitator dictionaryFacilitator =
+ mDictionaryFacilitatorCache.get(locale);
+ resetDictionariesForLocale(this /* context */,
+ dictionaryFacilitator, locale, mUseContactsDictionary);
+ }
+ } finally {
+ mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
+ }
}
- }
- contactsDict.close();
}
@Override
@@ -223,230 +218,114 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
EMPTY_STRING_ARRAY);
}
- public SuggestionsGatherer newSuggestionsGatherer(final String text, int maxLength) {
- return new SuggestionsGatherer(text, mRecommendedThreshold, maxLength);
+ public boolean isValidWord(final Locale locale, final String word) {
+ mSemaphore.acquireUninterruptibly();
+ try {
+ DictionaryFacilitator dictionaryFacilitatorForLocale =
+ getDictionaryFacilitatorForLocaleLocked(locale);
+ return dictionaryFacilitatorForLocale.isValidWord(word, false /* igroreCase */);
+ } finally {
+ mSemaphore.release();
+ }
}
- // TODO: remove this class and replace it by storage local to the session.
- public static final class SuggestionsGatherer {
- public static final class Result {
- public final String[] mSuggestions;
- public final boolean mHasRecommendedSuggestions;
- public Result(final String[] gatheredSuggestions,
- final boolean hasRecommendedSuggestions) {
- mSuggestions = gatheredSuggestions;
- mHasRecommendedSuggestions = hasRecommendedSuggestions;
+ public SuggestionResults getSuggestionResults(final Locale locale, final WordComposer composer,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo) {
+ Integer sessionId = null;
+ mSemaphore.acquireUninterruptibly();
+ try {
+ sessionId = mSessionIdPool.poll();
+ DictionaryFacilitator dictionaryFacilitatorForLocale =
+ getDictionaryFacilitatorForLocaleLocked(locale);
+ return dictionaryFacilitatorForLocale.getSuggestionResults(composer, prevWordsInfo,
+ proximityInfo, mSettingsValuesForSuggestion, sessionId);
+ } finally {
+ if (sessionId != null) {
+ mSessionIdPool.add(sessionId);
}
+ mSemaphore.release();
}
+ }
- private final ArrayList<String> mSuggestions;
- private final int[] mScores;
- private final String mOriginalText;
- private final float mRecommendedThreshold;
- private final int mMaxLength;
- private int mLength = 0;
-
- // The two following attributes are only ever filled if the requested max length
- // is 0 (or less, which is treated the same).
- private String mBestSuggestion = null;
- private int mBestScore = Integer.MIN_VALUE; // As small as possible
-
- SuggestionsGatherer(final String originalText, final float recommendedThreshold,
- final int maxLength) {
- mOriginalText = originalText;
- mRecommendedThreshold = recommendedThreshold;
- mMaxLength = maxLength;
- mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
- mScores = new int[mMaxLength];
+ public boolean hasMainDictionaryForLocale(final Locale locale) {
+ mSemaphore.acquireUninterruptibly();
+ try {
+ final DictionaryFacilitator dictionaryFacilitator =
+ getDictionaryFacilitatorForLocaleLocked(locale);
+ return dictionaryFacilitator.hasInitializedMainDictionary();
+ } finally {
+ mSemaphore.release();
}
+ }
- synchronized public boolean addWord(char[] word, int[] spaceIndices, int wordOffset,
- int wordLength, int score) {
- final int positionIndex = Arrays.binarySearch(mScores, 0, mLength, score);
- // binarySearch returns the index if the element exists, and -<insertion index> - 1
- // if it doesn't. See documentation for binarySearch.
- final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
-
- if (insertIndex == 0 && mLength >= mMaxLength) {
- // In the future, we may want to keep track of the best suggestion score even if
- // we are asked for 0 suggestions. In this case, we can use the following
- // (tested) code to keep it:
- // If the maxLength is 0 (should never be less, but if it is, it's treated as 0)
- // then we need to keep track of the best suggestion in mBestScore and
- // mBestSuggestion. This is so that we know whether the best suggestion makes
- // the score cutoff, since we need to know that to return a meaningful
- // looksLikeTypo.
- // if (0 >= mMaxLength) {
- // if (score > mBestScore) {
- // mBestScore = score;
- // mBestSuggestion = new String(word, wordOffset, wordLength);
- // }
- // }
- return true;
- }
- if (insertIndex >= mMaxLength) {
- // We found a suggestion, but its score is too weak to be kept considering
- // the suggestion limit.
- return true;
- }
-
- final String wordString = new String(word, wordOffset, wordLength);
- if (mLength < mMaxLength) {
- final int copyLen = mLength - insertIndex;
- ++mLength;
- System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
- mSuggestions.add(insertIndex, wordString);
- } else {
- System.arraycopy(mScores, 1, mScores, 0, insertIndex);
- mSuggestions.add(insertIndex, wordString);
- mSuggestions.remove(0);
- }
- mScores[insertIndex] = score;
-
- return true;
+ private DictionaryFacilitator getDictionaryFacilitatorForLocaleLocked(final Locale locale) {
+ DictionaryFacilitator dictionaryFacilitatorForLocale =
+ mDictionaryFacilitatorCache.get(locale);
+ if (dictionaryFacilitatorForLocale == null) {
+ dictionaryFacilitatorForLocale = new DictionaryFacilitator();
+ mDictionaryFacilitatorCache.put(locale, dictionaryFacilitatorForLocale);
+ mCachedLocales.add(locale);
+ resetDictionariesForLocale(this /* context */, dictionaryFacilitatorForLocale,
+ locale, mUseContactsDictionary);
}
+ return dictionaryFacilitatorForLocale;
+ }
- public Result getResults(final int capitalizeType, final Locale locale) {
- final String[] gatheredSuggestions;
- final boolean hasRecommendedSuggestions;
- if (0 == mLength) {
- // TODO: the comment below describes what is intended, but in the practice
- // mBestSuggestion is only ever set to null so it doesn't work. Fix this.
- // Either we found no suggestions, or we found some BUT the max length was 0.
- // If we found some mBestSuggestion will not be null. If it is null, then
- // we found none, regardless of the max length.
- if (null == mBestSuggestion) {
- gatheredSuggestions = null;
- hasRecommendedSuggestions = false;
+ private static void resetDictionariesForLocale(final Context context,
+ final DictionaryFacilitator dictionaryFacilitator, final Locale locale,
+ final boolean useContactsDictionary) {
+ dictionaryFacilitator.resetDictionariesWithDictNamePrefix(context, locale,
+ useContactsDictionary, false /* usePersonalizedDicts */,
+ false /* forceReloadMainDictionary */, null /* listener */,
+ DICTIONARY_NAME_PREFIX);
+ for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
+ try {
+ dictionaryFacilitator.waitForLoadingMainDictionary(
+ WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+ return;
+ } catch (final InterruptedException e) {
+ Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e);
+ if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) {
+ Log.i(TAG, "Retry", e);
} else {
- gatheredSuggestions = EMPTY_STRING_ARRAY;
- final float normalizedScore = BinaryDictionary.calcNormalizedScore(
- mOriginalText, mBestSuggestion, mBestScore);
- hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
- }
- } else {
- if (DBG) {
- if (mLength != mSuggestions.size()) {
- Log.e(TAG, "Suggestion size is not the same as stored mLength");
- }
- for (int i = mLength - 1; i >= 0; --i) {
- Log.i(TAG, "" + mScores[i] + " " + mSuggestions.get(i));
- }
- }
- Collections.reverse(mSuggestions);
- StringUtils.removeDupes(mSuggestions);
- if (StringUtils.CAPITALIZE_ALL == capitalizeType) {
- for (int i = 0; i < mSuggestions.size(); ++i) {
- // get(i) returns a CharSequence which is actually a String so .toString()
- // should return the same object.
- mSuggestions.set(i, mSuggestions.get(i).toString().toUpperCase(locale));
- }
- } else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) {
- for (int i = 0; i < mSuggestions.size(); ++i) {
- // Likewise
- mSuggestions.set(i, StringUtils.capitalizeFirstCodePoint(
- mSuggestions.get(i).toString(), locale));
- }
- }
- // This returns a String[], while toArray() returns an Object[] which cannot be cast
- // into a String[].
- gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY);
-
- final int bestScore = mScores[mLength - 1];
- final String bestSuggestion = mSuggestions.get(0);
- final float normalizedScore =
- BinaryDictionary.calcNormalizedScore(
- mOriginalText, bestSuggestion.toString(), bestScore);
- hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
- if (DBG) {
- Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
- Log.i(TAG, "Normalized score = " + normalizedScore
- + " (threshold " + mRecommendedThreshold
- + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions);
+ Log.w(TAG, "Give up retrying. Retried "
+ + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e);
}
}
- return new Result(gatheredSuggestions, hasRecommendedSuggestions);
}
}
@Override
public boolean onUnbind(final Intent intent) {
- closeAllDictionaries();
+ mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
+ try {
+ mDictionaryFacilitatorCache.evictAll();
+ mCachedLocales.clear();
+ } finally {
+ mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
+ }
+ mKeyboardCache.clear();
return false;
}
- private void closeAllDictionaries() {
- final Map<String, DictionaryPool> oldPools = mDictionaryPools;
- mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
- final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
- mUserDictionaries = CollectionUtils.newSynchronizedTreeMap();
- new Thread("spellchecker_close_dicts") {
- @Override
- public void run() {
- for (DictionaryPool pool : oldPools.values()) {
- pool.close();
- }
- for (Dictionary dict : oldUserDictionaries.values()) {
- dict.close();
- }
- synchronized (mUseContactsLock) {
- if (null != mContactsDictionary) {
- // The synchronously loaded contacts dictionary should have been in one
- // or several pools, but it is shielded against multiple closing and it's
- // safe to call it several times.
- final ContactsBinaryDictionary dictToClose = mContactsDictionary;
- // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY
- // is no longer needed
- mContactsDictionary = null;
- dictToClose.close();
- }
- }
+ public Keyboard getKeyboardForLocale(final Locale locale) {
+ Keyboard keyboard = mKeyboardCache.get(locale);
+ if (keyboard == null) {
+ keyboard = createKeyboardForLocale(locale);
+ if (keyboard != null) {
+ mKeyboardCache.put(locale, keyboard);
}
- }.start();
- }
-
- public DictionaryPool getDictionaryPool(final String locale) {
- DictionaryPool pool = mDictionaryPools.get(locale);
- if (null == pool) {
- final Locale localeObject = LocaleUtils.constructLocaleFromString(locale);
- pool = new DictionaryPool(POOL_SIZE, this, localeObject);
- mDictionaryPools.put(locale, pool);
}
- return pool;
+ return keyboard;
}
- public DictAndKeyboard createDictAndKeyboard(final Locale locale) {
- final int script = getScriptFromLocale(locale);
+ private Keyboard createKeyboardForLocale(final Locale locale) {
+ final int script = ScriptUtils.getScriptFromSpellCheckerLocale(locale);
final String keyboardLayoutName = getKeyboardLayoutNameForScript(script);
- final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype(
- locale.toString(), keyboardLayoutName, null);
+ final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype(
+ locale.toString(), keyboardLayoutName);
final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype);
-
- final DictionaryCollection dictionaryCollection =
- DictionaryFactory.createMainDictionaryFromManager(this, locale,
- true /* useFullEditDistance */);
- final String localeStr = locale.toString();
- UserBinaryDictionary userDictionary = mUserDictionaries.get(localeStr);
- if (null == userDictionary) {
- userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true);
- mUserDictionaries.put(localeStr, userDictionary);
- }
- dictionaryCollection.addDictionary(userDictionary);
- synchronized (mUseContactsLock) {
- if (mUseContactsDictionary) {
- if (null == mContactsDictionary) {
- // TODO: use the right locale. We can't do it right now because the
- // spell checker is reusing the contacts dictionary across sessions
- // without regard for their locale, so we need to fix that first.
- mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this,
- Locale.getDefault());
- }
- }
- dictionaryCollection.addDictionary(mContactsDictionary);
- mDictionaryCollectionsList.add(
- new WeakReference<DictionaryCollection>(dictionaryCollection));
- }
- return new DictAndKeyboard(dictionaryCollection, keyboardLayoutSet);
+ return keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
}
private KeyboardLayoutSet createKeyboardSetForSpellChecker(final InputMethodSubtype subtype) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index ddda52d71..14ab2dbbf 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin.spellcheck;
+import android.content.res.Resources;
import android.os.Binder;
import android.text.TextUtils;
import android.util.Log;
@@ -23,31 +24,35 @@ import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.compat.TextInfoCompatUtils;
+import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
+import java.util.Locale;
public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName();
private static final boolean DBG = false;
- private final static String[] EMPTY_STRING_ARRAY = new String[0];
+ private final Resources mResources;
+ private SentenceLevelAdapter mSentenceLevelAdapter;
public AndroidSpellCheckerSession(AndroidSpellCheckerService service) {
super(service);
+ mResources = service.getResources();
}
private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti,
SentenceSuggestionsInfo ssi) {
- final String typedText = ti.getText();
- if (!typedText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
+ final CharSequence typedText = TextInfoCompatUtils.getCharSequenceOrString(ti);
+ if (!typedText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
return null;
}
final int N = ssi.getSuggestionsCount();
- final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
- final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
- final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
- CollectionUtils.newArrayList();
- String currentWord = null;
+ final ArrayList<Integer> additionalOffsets = new ArrayList<>();
+ final ArrayList<Integer> additionalLengths = new ArrayList<>();
+ final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = new ArrayList<>();
+ CharSequence currentWord = null;
for (int i = 0; i < N; ++i) {
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
final int flags = si.getSuggestionsAttributes();
@@ -56,31 +61,33 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
}
final int offset = ssi.getOffsetAt(i);
final int length = ssi.getLengthAt(i);
- final String subText = typedText.substring(offset, offset + length);
- final String prevWord = currentWord;
+ final CharSequence subText = typedText.subSequence(offset, offset + length);
+ final PrevWordsInfo prevWordsInfo =
+ new PrevWordsInfo(new PrevWordsInfo.WordInfo(currentWord));
currentWord = subText;
- if (!subText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
+ if (!subText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
continue;
}
- final String[] splitTexts =
- subText.split(AndroidSpellCheckerService.SINGLE_QUOTE, -1);
+ final CharSequence[] splitTexts = StringUtils.split(subText,
+ AndroidSpellCheckerService.SINGLE_QUOTE,
+ true /* preserveTrailingEmptySegments */ );
if (splitTexts == null || splitTexts.length <= 1) {
continue;
}
final int splitNum = splitTexts.length;
for (int j = 0; j < splitNum; ++j) {
- final String splitText = splitTexts[j];
+ final CharSequence splitText = splitTexts[j];
if (TextUtils.isEmpty(splitText)) {
continue;
}
- if (mSuggestionsCache.getSuggestionsFromCache(splitText, prevWord) == null) {
+ if (mSuggestionsCache.getSuggestionsFromCache(splitText.toString(), prevWordsInfo)
+ == null) {
continue;
}
final int newLength = splitText.length();
// Neither RESULT_ATTR_IN_THE_DICTIONARY nor RESULT_ATTR_LOOKS_LIKE_TYPO
final int newFlags = 0;
- final SuggestionsInfo newSi =
- new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY);
+ final SuggestionsInfo newSi = new SuggestionsInfo(newFlags, EMPTY_STRING_ARRAY);
newSi.setCookieAndSequence(si.getCookie(), si.getSequence());
if (DBG) {
Log.d(TAG, "Override and remove old span over: " + splitText + ", "
@@ -116,8 +123,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
@Override
public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos,
int suggestionsLimit) {
- final SentenceSuggestionsInfo[] retval =
- super.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit);
+ final SentenceSuggestionsInfo[] retval = splitAndSuggest(textInfos, suggestionsLimit);
if (retval == null || retval.length != textInfos.length) {
return retval;
}
@@ -131,6 +137,58 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
return retval;
}
+ /**
+ * Get sentence suggestions for specified texts in an array of TextInfo. This is taken from
+ * SpellCheckerService#onGetSentenceSuggestionsMultiple that we can't use because it's
+ * using private variables.
+ * The default implementation splits the input text to words and returns
+ * {@link SentenceSuggestionsInfo} which contains suggestions for each word.
+ * This function will run on the incoming IPC thread.
+ * So, this is not called on the main thread,
+ * but will be called in series on another thread.
+ * @param textInfos an array of the text metadata
+ * @param suggestionsLimit the maximum number of suggestions to be returned
+ * @return an array of {@link SentenceSuggestionsInfo} returned by
+ * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
+ */
+ private SentenceSuggestionsInfo[] splitAndSuggest(TextInfo[] textInfos, int suggestionsLimit) {
+ if (textInfos == null || textInfos.length == 0) {
+ return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+ }
+ SentenceLevelAdapter sentenceLevelAdapter;
+ synchronized(this) {
+ sentenceLevelAdapter = mSentenceLevelAdapter;
+ if (sentenceLevelAdapter == null) {
+ final String localeStr = getLocale();
+ if (!TextUtils.isEmpty(localeStr)) {
+ sentenceLevelAdapter = new SentenceLevelAdapter(mResources,
+ new Locale(localeStr));
+ mSentenceLevelAdapter = sentenceLevelAdapter;
+ }
+ }
+ }
+ if (sentenceLevelAdapter == null) {
+ return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+ }
+ final int infosSize = textInfos.length;
+ final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[infosSize];
+ for (int i = 0; i < infosSize; ++i) {
+ final SentenceLevelAdapter.SentenceTextInfoParams textInfoParams =
+ sentenceLevelAdapter.getSplitWords(textInfos[i]);
+ final ArrayList<SentenceLevelAdapter.SentenceWordItem> mItems =
+ textInfoParams.mItems;
+ final int itemsSize = mItems.size();
+ final TextInfo[] splitTextInfos = new TextInfo[itemsSize];
+ for (int j = 0; j < itemsSize; ++j) {
+ splitTextInfos[j] = mItems.get(j).mTextInfo;
+ }
+ retval[i] = SentenceLevelAdapter.reconstructSuggestions(
+ textInfoParams, onGetSuggestionsMultiple(
+ splitTextInfos, suggestionsLimit, true));
+ }
+ return retval;
+ }
+
@Override
public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos,
int suggestionsLimit, boolean sequentialWords) {
@@ -139,18 +197,22 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
final int length = textInfos.length;
final SuggestionsInfo[] retval = new SuggestionsInfo[length];
for (int i = 0; i < length; ++i) {
- final String prevWord;
+ final CharSequence prevWord;
if (sequentialWords && i > 0) {
- final String prevWordCandidate = textInfos[i - 1].getText();
- // Note that an empty string would be used to indicate the initial word
- // in the future.
- prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate;
+ final TextInfo prevTextInfo = textInfos[i - 1];
+ final CharSequence prevWordCandidate =
+ TextInfoCompatUtils.getCharSequenceOrString(prevTextInfo);
+ // Note that an empty string would be used to indicate the initial word
+ // in the future.
+ prevWord = TextUtils.isEmpty(prevWordCandidate) ? null : prevWordCandidate;
} else {
prevWord = null;
}
- retval[i] = onGetSuggestionsInternal(textInfos[i], prevWord, suggestionsLimit);
- retval[i].setCookieAndSequence(textInfos[i].getCookie(),
- textInfos[i].getSequence());
+ final PrevWordsInfo prevWordsInfo =
+ new PrevWordsInfo(new PrevWordsInfo.WordInfo(prevWord));
+ final TextInfo textInfo = textInfos[i];
+ retval[i] = onGetSuggestionsInternal(textInfo, prevWordsInfo, suggestionsLimit);
+ retval[i].setCookieAndSequence(textInfo.getCookie(), textInfo.getSequence());
}
return retval;
} finally {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index d6e5b75ad..d668672aa 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -28,13 +28,18 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.ScriptUtils;
import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.SuggestionResults;
import java.util.ArrayList;
import java.util.Locale;
@@ -43,9 +48,9 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
private static final String TAG = AndroidWordLevelSpellCheckerSession.class.getSimpleName();
private static final boolean DBG = false;
- // Immutable, but need the locale which is not available in the constructor yet
- private DictionaryPool mDictionaryPool;
- // Likewise
+ public final static String[] EMPTY_STRING_ARRAY = new String[0];
+
+ // Immutable, but not available in the constructor.
private Locale mLocale;
// Cache this for performance
private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now.
@@ -66,29 +71,29 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
private static final char CHAR_DELIMITER = '\uFFFC';
private static final int MAX_CACHE_SIZE = 50;
private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
- new LruCache<String, SuggestionsParams>(MAX_CACHE_SIZE);
+ new LruCache<>(MAX_CACHE_SIZE);
// TODO: Support n-gram input
- private static String generateKey(String query, String prevWord) {
- if (TextUtils.isEmpty(query) || TextUtils.isEmpty(prevWord)) {
+ private static String generateKey(final String query, final PrevWordsInfo prevWordsInfo) {
+ if (TextUtils.isEmpty(query) || !prevWordsInfo.isValid()) {
return query;
}
- return query + CHAR_DELIMITER + prevWord;
+ return query + CHAR_DELIMITER + prevWordsInfo;
}
- // TODO: Support n-gram input
- public SuggestionsParams getSuggestionsFromCache(String query, String prevWord) {
- return mUnigramSuggestionsInfoCache.get(generateKey(query, prevWord));
+ public SuggestionsParams getSuggestionsFromCache(String query,
+ final PrevWordsInfo prevWordsInfo) {
+ return mUnigramSuggestionsInfoCache.get(generateKey(query, prevWordsInfo));
}
- // TODO: Support n-gram input
public void putSuggestionsToCache(
- String query, String prevWord, String[] suggestions, int flags) {
+ final String query, final PrevWordsInfo prevWordsInfo,
+ final String[] suggestions, final int flags) {
if (suggestions == null || TextUtils.isEmpty(query)) {
return;
}
mUnigramSuggestionsInfoCache.put(
- generateKey(query, prevWord), new SuggestionsParams(suggestions, flags));
+ generateKey(query, prevWordsInfo), new SuggestionsParams(suggestions, flags));
}
public void clearCache() {
@@ -112,9 +117,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
@Override
public void onCreate() {
final String localeString = getLocale();
- mDictionaryPool = mService.getDictionaryPool(localeString);
mLocale = LocaleUtils.constructLocaleFromString(localeString);
- mScript = AndroidSpellCheckerService.getScriptFromLocale(mLocale);
+ mScript = ScriptUtils.getScriptFromSpellCheckerLocale(mLocale);
}
@Override
@@ -123,44 +127,6 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
cres.unregisterContentObserver(mObserver);
}
- /*
- * Returns whether the code point is a letter that makes sense for the specified
- * locale for this spell checker.
- * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml
- * and is limited to EFIGS languages and Russian.
- * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters
- * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters.
- */
- private static boolean isLetterCheckableByLanguage(final int codePoint,
- final int script) {
- switch (script) {
- case AndroidSpellCheckerService.SCRIPT_LATIN:
- // Our supported latin script dictionaries (EFIGS) at the moment only include
- // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode
- // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF,
- // so the below is a very efficient way to test for it. As for the 0-0x3F, it's
- // excluded from isLetter anyway.
- return codePoint <= 0x2AF && Character.isLetter(codePoint);
- case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
- // All Cyrillic characters are in the 400~52F block. There are some in the upper
- // Unicode range, but they are archaic characters that are not used in modern
- // Russian and are not used by our dictionary.
- return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
- case AndroidSpellCheckerService.SCRIPT_GREEK:
- // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the
- // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters.
- // Our dictionary also contains a few words with 0xF2; it would be best to check
- // if that's correct, but a web search does return results for these words so
- // they are probably okay.
- return (codePoint >= 0x370 && codePoint <= 0x3FF)
- || (codePoint >= 0x1F00 && codePoint <= 0x1FFF)
- || codePoint == 0xF2;
- default:
- // Should never come here
- throw new RuntimeException("Impossible value of script: " + script);
- }
- }
-
private static final int CHECKABILITY_CHECKABLE = 0;
private static final int CHECKABILITY_TOO_MANY_NON_LETTERS = 1;
private static final int CHECKABILITY_CONTAINS_PERIOD = 2;
@@ -187,7 +153,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
// Filter by first letter
final int firstCodePoint = text.codePointAt(0);
// Filter out words that don't start with a letter or an apostrophe
- if (!isLetterCheckableByLanguage(firstCodePoint, script)
+ if (!ScriptUtils.isLetterPartOfScript(firstCodePoint, script)
&& '\'' != firstCodePoint) return CHECKABILITY_FIRST_LETTER_UNCHECKABLE;
// Filter contents
@@ -208,7 +174,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
if (Constants.CODE_PERIOD == codePoint) {
return CHECKABILITY_CONTAINS_PERIOD;
}
- if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
+ if (ScriptUtils.isLetterPartOfScript(codePoint, script)) ++letterCount;
}
// Guestimate heuristic: perform spell checking if at least 3/4 of the characters
// in this word are letters
@@ -225,24 +191,24 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
* If the "TEXT" is fully upper case, we test the exact string "TEXT", the lower-cased
* version of it "text" and the capitalized version of it "Text".
*/
- private boolean isInDictForAnyCapitalization(final Dictionary dict, final String text,
- final int capitalizeType) {
+ private boolean isInDictForAnyCapitalization(final String text, final int capitalizeType) {
// If the word is in there as is, then it's in the dictionary. If not, we'll test lower
// case versions, but only if the word is not already all-lower case or mixed case.
- if (dict.isValidWord(text)) return true;
+ if (mService.isValidWord(mLocale, text)) return true;
if (StringUtils.CAPITALIZE_NONE == capitalizeType) return false;
// If we come here, we have a capitalized word (either First- or All-).
// Downcase the word and look it up again. If the word is only capitalized, we
// tested all possibilities, so if it's still negative we can return false.
final String lowerCaseText = text.toLowerCase(mLocale);
- if (dict.isValidWord(lowerCaseText)) return true;
+ if (mService.isValidWord(mLocale, lowerCaseText)) return true;
if (StringUtils.CAPITALIZE_FIRST == capitalizeType) return false;
// If the lower case version is not in the dictionary, it's still possible
// that we have an all-caps version of a word that needs to be capitalized
// according to the dictionary. E.g. "GERMANS" only exists in the dictionary as "Germans".
- return dict.isValidWord(StringUtils.capitalizeFirstAndDowncaseRest(lowerCaseText, mLocale));
+ return mService.isValidWord(mLocale,
+ StringUtils.capitalizeFirstAndDowncaseRest(lowerCaseText, mLocale));
}
// Note : this must be reentrant
@@ -257,11 +223,12 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
protected SuggestionsInfo onGetSuggestionsInternal(
- final TextInfo textInfo, final String prevWord, final int suggestionsLimit) {
+ final TextInfo textInfo, final PrevWordsInfo prevWordsInfo,
+ final int suggestionsLimit) {
try {
final String inText = textInfo.getText();
final SuggestionsParams cachedSuggestionsParams =
- mSuggestionsCache.getSuggestionsFromCache(inText, prevWord);
+ mSuggestionsCache.getSuggestionsFromCache(inText, prevWordsInfo);
if (cachedSuggestionsParams != null) {
if (DBG) {
Log.d(TAG, "Cache hit: " + inText + ", " + cachedSuggestionsParams.mFlags);
@@ -269,78 +236,57 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
return new SuggestionsInfo(
cachedSuggestionsParams.mFlags, cachedSuggestionsParams.mSuggestions);
}
-
final int checkability = getCheckabilityInScript(inText, mScript);
if (CHECKABILITY_CHECKABLE != checkability) {
- DictAndKeyboard dictInfo = null;
- try {
- dictInfo = mDictionaryPool.pollWithDefaultTimeout();
- if (!DictionaryPool.isAValidDictionary(dictInfo)) {
- return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
- false /* reportAsTypo */);
- }
- return dictInfo.mDictionary.isValidWord(inText)
- ? AndroidSpellCheckerService.getInDictEmptySuggestions()
- : AndroidSpellCheckerService.getNotInDictEmptySuggestions(
- CHECKABILITY_CONTAINS_PERIOD == checkability
- /* reportAsTypo */);
- } finally {
- if (null != dictInfo) {
- if (!mDictionaryPool.offer(dictInfo)) {
- Log.e(TAG, "Can't re-insert a dictionary into its pool");
+ if (CHECKABILITY_CONTAINS_PERIOD == checkability) {
+ final String[] splitText = inText.split(Constants.REGEXP_PERIOD);
+ boolean allWordsAreValid = true;
+ for (final String word : splitText) {
+ if (!mService.isValidWord(mLocale, word)) {
+ allWordsAreValid = false;
+ break;
}
}
+ if (allWordsAreValid) {
+ return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
+ | SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS,
+ new String[] {
+ TextUtils.join(Constants.STRING_SPACE, splitText) });
+ }
}
+ return mService.isValidWord(mLocale, inText) ?
+ AndroidSpellCheckerService.getInDictEmptySuggestions() :
+ AndroidSpellCheckerService.getNotInDictEmptySuggestions(
+ CHECKABILITY_CONTAINS_PERIOD == checkability /* reportAsTypo */);
}
final String text = inText.replaceAll(
AndroidSpellCheckerService.APOSTROPHE, AndroidSpellCheckerService.SINGLE_QUOTE);
-
- // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
- //final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text,
- //mService.mSuggestionThreshold, mService.mRecommendedThreshold,
- //suggestionsLimit);
- final SuggestionsGatherer suggestionsGatherer = mService.newSuggestionsGatherer(
- text, suggestionsLimit);
-
final int capitalizeType = StringUtils.getCapitalizationType(text);
boolean isInDict = true;
- DictAndKeyboard dictInfo = null;
- try {
- dictInfo = mDictionaryPool.pollWithDefaultTimeout();
- if (!DictionaryPool.isAValidDictionary(dictInfo)) {
- return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
- false /* reportAsTypo */);
- }
- final WordComposer composer = new WordComposer();
- final int length = text.length();
- for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
- final int codePoint = text.codePointAt(i);
- composer.addKeyInfo(codePoint, dictInfo.getKeyboard(codePoint));
- }
- // TODO: make a spell checker option to block offensive words or not
- final ArrayList<SuggestedWordInfo> suggestions =
- dictInfo.mDictionary.getSuggestions(composer, prevWord,
- dictInfo.getProximityInfo(), true /* blockOffensiveWords */,
- null /* additionalFeaturesOptions */);
- if (suggestions != null) {
- for (final SuggestedWordInfo suggestion : suggestions) {
- final String suggestionStr = suggestion.mWord;
- suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
- suggestionStr.length(), suggestion.mScore);
- }
- }
- isInDict = isInDictForAnyCapitalization(dictInfo.mDictionary, text, capitalizeType);
- } finally {
- if (null != dictInfo) {
- if (!mDictionaryPool.offer(dictInfo)) {
- Log.e(TAG, "Can't re-insert a dictionary into its pool");
- }
- }
+ if (!mService.hasMainDictionaryForLocale(mLocale)) {
+ return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
+ false /* reportAsTypo */);
}
-
- final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(
- capitalizeType, mLocale);
-
+ final Keyboard keyboard = mService.getKeyboardForLocale(mLocale);
+ final WordComposer composer = new WordComposer();
+ final int[] codePoints = StringUtils.toCodePointArray(text);
+ final int[] coordinates;
+ final ProximityInfo proximityInfo;
+ if (null == keyboard) {
+ coordinates = CoordinateUtils.newCoordinateArray(codePoints.length,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ proximityInfo = null;
+ } else {
+ coordinates = keyboard.getCoordinates(codePoints);
+ proximityInfo = keyboard.getProximityInfo();
+ }
+ composer.setComposingWord(codePoints, coordinates);
+ // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
+ final SuggestionResults suggestionResults = mService.getSuggestionResults(
+ mLocale, composer, prevWordsInfo, proximityInfo);
+ final Result result = getResult(capitalizeType, mLocale, suggestionsLimit,
+ mService.getRecommendedThreshold(), text, suggestionResults);
+ isInDict = isInDictForAnyCapitalization(text, capitalizeType);
if (DBG) {
Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "
+ suggestionsLimit);
@@ -362,7 +308,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
.getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
: 0);
final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions);
- mSuggestionsCache.putSuggestionsToCache(text, prevWord, result.mSuggestions, flags);
+ mSuggestionsCache.putSuggestionsToCache(text, prevWordsInfo, result.mSuggestions,
+ flags);
return retval;
} catch (RuntimeException e) {
// Don't kill the keyboard if there is a bug in the spell checker
@@ -376,6 +323,62 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
}
+ private static final class Result {
+ public final String[] mSuggestions;
+ public final boolean mHasRecommendedSuggestions;
+ public Result(final String[] gatheredSuggestions,
+ final boolean hasRecommendedSuggestions) {
+ mSuggestions = gatheredSuggestions;
+ mHasRecommendedSuggestions = hasRecommendedSuggestions;
+ }
+ }
+
+ private static Result getResult(final int capitalizeType, final Locale locale,
+ final int suggestionsLimit, final float recommendedThreshold, final String originalText,
+ final SuggestionResults suggestionResults) {
+ if (suggestionResults.isEmpty() || suggestionsLimit <= 0) {
+ return new Result(null /* gatheredSuggestions */,
+ false /* hasRecommendedSuggestions */);
+ }
+ if (DBG) {
+ for (final SuggestedWordInfo suggestedWordInfo : suggestionResults) {
+ Log.i(TAG, "" + suggestedWordInfo.mScore + " " + suggestedWordInfo.mWord);
+ }
+ }
+ final ArrayList<String> suggestions = new ArrayList<>();
+ for (final SuggestedWordInfo suggestedWordInfo : suggestionResults) {
+ final String suggestion;
+ if (StringUtils.CAPITALIZE_ALL == capitalizeType) {
+ suggestion = suggestedWordInfo.mWord.toUpperCase(locale);
+ } else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) {
+ suggestion = StringUtils.capitalizeFirstCodePoint(
+ suggestedWordInfo.mWord, locale);
+ } else {
+ suggestion = suggestedWordInfo.mWord;
+ }
+ suggestions.add(suggestion);
+ }
+ StringUtils.removeDupes(suggestions);
+ // This returns a String[], while toArray() returns an Object[] which cannot be cast
+ // into a String[].
+ final String[] gatheredSuggestions =
+ suggestions.subList(0, Math.min(suggestions.size(), suggestionsLimit))
+ .toArray(EMPTY_STRING_ARRAY);
+
+ final int bestScore = suggestionResults.first().mScore;
+ final String bestSuggestion = suggestions.get(0);
+ final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
+ originalText, bestSuggestion.toString(), bestScore);
+ final boolean hasRecommendedSuggestions = (normalizedScore > recommendedThreshold);
+ if (DBG) {
+ Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
+ Log.i(TAG, "Normalized score = " + normalizedScore
+ + " (threshold " + recommendedThreshold
+ + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions);
+ }
+ return new Result(gatheredSuggestions, hasRecommendedSuggestions);
+ }
+
/*
* The spell checker acts on its own behalf. That is needed, in particular, to be able to
* access the dictionary files, which the provider restricts to the identity of Latin IME.
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
deleted file mode 100644
index b77f3e2c5..000000000
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.latin.Dictionary;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.keyboard.ProximityInfo;
-
-/**
- * A container for a Dictionary and a Keyboard.
- */
-public final class DictAndKeyboard {
- public final Dictionary mDictionary;
- private final Keyboard mKeyboard;
- private final Keyboard mManualShiftedKeyboard;
-
- public DictAndKeyboard(
- final Dictionary dictionary, final KeyboardLayoutSet keyboardLayoutSet) {
- mDictionary = dictionary;
- if (keyboardLayoutSet == null) {
- mKeyboard = null;
- mManualShiftedKeyboard = null;
- return;
- }
- mKeyboard = keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
- mManualShiftedKeyboard =
- keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED);
- }
-
- public Keyboard getKeyboard(final int codePoint) {
- if (mKeyboard == null) {
- return null;
- }
- return mKeyboard.getKey(codePoint) != null ? mKeyboard : mManualShiftedKeyboard;
- }
-
- public ProximityInfo getProximityInfo() {
- return mKeyboard == null ? null : mKeyboard.getProximityInfo();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
deleted file mode 100644
index a0aed2829..000000000
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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 android.util.Log;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
-import java.util.ArrayList;
-import java.util.Locale;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A blocking queue that creates dictionaries up to a certain limit as necessary.
- * As a deadlock-detecting device, if waiting for more than TIMEOUT = 3 seconds, we
- * will clear the queue and generate its contents again. This is transparent for
- * the client code, but may help with sloppy clients.
- */
-@SuppressWarnings("serial")
-public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> {
- private final static String TAG = DictionaryPool.class.getSimpleName();
- // How many seconds we wait for a dictionary to become available. Past this delay, we give up in
- // fear some bug caused a deadlock, and reset the whole pool.
- private final static int TIMEOUT = 3;
- private final AndroidSpellCheckerService mService;
- private final int mMaxSize;
- private final Locale mLocale;
- private int mSize;
- private volatile boolean mClosed;
- final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
- private final static DictAndKeyboard dummyDict = new DictAndKeyboard(
- new Dictionary(Dictionary.TYPE_MAIN) {
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
- return noSuggestions;
- }
- @Override
- public boolean isValidWord(final String word) {
- // This is never called. However if for some strange reason it ever gets
- // called, returning true is less destructive (it will not underline the
- // word in red).
- return true;
- }
- }, null);
-
- static public boolean isAValidDictionary(final DictAndKeyboard dictInfo) {
- return null != dictInfo && dummyDict != dictInfo;
- }
-
- public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
- final Locale locale) {
- super();
- mMaxSize = maxSize;
- mService = service;
- mLocale = locale;
- mSize = 0;
- mClosed = false;
- }
-
- @Override
- public DictAndKeyboard poll(final long timeout, final TimeUnit unit)
- throws InterruptedException {
- final DictAndKeyboard dict = poll();
- if (null != dict) return dict;
- synchronized(this) {
- if (mSize >= mMaxSize) {
- // Our pool is already full. Wait until some dictionary is ready, or TIMEOUT
- // expires to avoid a deadlock.
- final DictAndKeyboard result = super.poll(timeout, unit);
- if (null == result) {
- Log.e(TAG, "Deadlock detected ! Resetting dictionary pool");
- clear();
- mSize = 1;
- return mService.createDictAndKeyboard(mLocale);
- } else {
- return result;
- }
- } else {
- ++mSize;
- return mService.createDictAndKeyboard(mLocale);
- }
- }
- }
-
- // Convenience method
- public DictAndKeyboard pollWithDefaultTimeout() {
- try {
- return poll(TIMEOUT, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- return null;
- }
- }
-
- public void close() {
- synchronized(this) {
- mClosed = true;
- for (DictAndKeyboard dict : this) {
- dict.mDictionary.close();
- }
- clear();
- }
- }
-
- @Override
- public boolean offer(final DictAndKeyboard dict) {
- if (mClosed) {
- dict.mDictionary.close();
- return super.offer(dummyDict);
- } else {
- return super.offer(dict);
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
new file mode 100644
index 000000000..ae582ea25
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2014 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 android.content.res.Resources;
+import android.view.textservice.SentenceSuggestionsInfo;
+import android.view.textservice.SuggestionsInfo;
+import android.view.textservice.TextInfo;
+
+import com.android.inputmethod.compat.TextInfoCompatUtils;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.RunInLocale;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * This code is mostly lifted directly from android.service.textservice.SpellCheckerService in
+ * the framework; maybe that should be protected instead, so that implementers don't have to
+ * rewrite everything for any small change.
+ */
+public class SentenceLevelAdapter {
+ public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS =
+ new SentenceSuggestionsInfo[] {};
+ private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null);
+ /**
+ * Container for split TextInfo parameters
+ */
+ public static class SentenceWordItem {
+ public final TextInfo mTextInfo;
+ public final int mStart;
+ public final int mLength;
+ public SentenceWordItem(TextInfo ti, int start, int end) {
+ mTextInfo = ti;
+ mStart = start;
+ mLength = end - start;
+ }
+ }
+
+ /**
+ * Container for originally queried TextInfo and parameters
+ */
+ public static class SentenceTextInfoParams {
+ final TextInfo mOriginalTextInfo;
+ final ArrayList<SentenceWordItem> mItems;
+ final int mSize;
+ public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) {
+ mOriginalTextInfo = ti;
+ mItems = items;
+ mSize = items.size();
+ }
+ }
+
+ private static class WordIterator {
+ private final SpacingAndPunctuations mSpacingAndPunctuations;
+ public WordIterator(final Resources res, final Locale locale) {
+ final RunInLocale<SpacingAndPunctuations> job
+ = new RunInLocale<SpacingAndPunctuations>() {
+ @Override
+ protected SpacingAndPunctuations job(final Resources res) {
+ return new SpacingAndPunctuations(res);
+ }
+ };
+ mSpacingAndPunctuations = job.runInLocale(res, locale);
+ }
+
+ public int getEndOfWord(final CharSequence sequence, int index) {
+ final int length = sequence.length();
+ index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1);
+ while (index < length) {
+ final int codePoint = Character.codePointAt(sequence, index);
+ if (mSpacingAndPunctuations.isWordSeparator(codePoint)) {
+ // If it's a period, we want to stop here only if it's followed by another
+ // word separator. In all other cases we stop here.
+ if (Constants.CODE_PERIOD == codePoint) {
+ final int indexOfNextCodePoint =
+ index + Character.charCount(Constants.CODE_PERIOD);
+ if (indexOfNextCodePoint < length
+ && mSpacingAndPunctuations.isWordSeparator(
+ Character.codePointAt(sequence, indexOfNextCodePoint))) {
+ return index;
+ }
+ } else {
+ return index;
+ }
+ }
+ index += Character.charCount(codePoint);
+ }
+ return index;
+ }
+
+ public int getBeginningOfNextWord(final CharSequence sequence, int index) {
+ final int length = sequence.length();
+ if (index >= length) {
+ return -1;
+ }
+ index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1);
+ while (index < length) {
+ final int codePoint = Character.codePointAt(sequence, index);
+ if (!mSpacingAndPunctuations.isWordSeparator(codePoint)) {
+ return index;
+ }
+ index += Character.charCount(codePoint);
+ }
+ return -1;
+ }
+ }
+
+ private final WordIterator mWordIterator;
+ public SentenceLevelAdapter(final Resources res, final Locale locale) {
+ mWordIterator = new WordIterator(res, locale);
+ }
+
+ public SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) {
+ final WordIterator wordIterator = mWordIterator;
+ final CharSequence originalText =
+ TextInfoCompatUtils.getCharSequenceOrString(originalTextInfo);
+ final int cookie = originalTextInfo.getCookie();
+ final int start = -1;
+ final int end = originalText.length();
+ final ArrayList<SentenceWordItem> wordItems = new ArrayList<SentenceWordItem>();
+ int wordStart = wordIterator.getBeginningOfNextWord(originalText, start);
+ int wordEnd = wordIterator.getEndOfWord(originalText, wordStart);
+ while (wordStart <= end && wordEnd != -1 && wordStart != -1) {
+ if (wordEnd >= start && wordEnd > wordStart) {
+ CharSequence subSequence = originalText.subSequence(wordStart, wordEnd).toString();
+ final TextInfo ti = TextInfoCompatUtils.newInstance(subSequence, 0,
+ subSequence.length(), cookie, subSequence.hashCode());
+ wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd));
+ }
+ wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd);
+ if (wordStart == -1) {
+ break;
+ }
+ wordEnd = wordIterator.getEndOfWord(originalText, wordStart);
+ }
+ return new SentenceTextInfoParams(originalTextInfo, wordItems);
+ }
+
+ public static SentenceSuggestionsInfo reconstructSuggestions(
+ SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) {
+ if (results == null || results.length == 0) {
+ return null;
+ }
+ if (originalTextInfoParams == null) {
+ return null;
+ }
+ final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie();
+ final int originalSequence =
+ originalTextInfoParams.mOriginalTextInfo.getSequence();
+
+ final int querySize = originalTextInfoParams.mSize;
+ final int[] offsets = new int[querySize];
+ final int[] lengths = new int[querySize];
+ final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize];
+ for (int i = 0; i < querySize; ++i) {
+ final SentenceWordItem item = originalTextInfoParams.mItems.get(i);
+ SuggestionsInfo result = null;
+ for (int j = 0; j < results.length; ++j) {
+ final SuggestionsInfo cur = results[j];
+ if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) {
+ result = cur;
+ result.setCookieAndSequence(originalCookie, originalSequence);
+ break;
+ }
+ }
+ offsets[i] = item.mStart;
+ lengths[i] = item.mLength;
+ reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO;
+ }
+ return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index 999ca775b..6850e9b58 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -21,26 +21,20 @@ import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.settings.TwoStatePreferenceHelper;
import com.android.inputmethod.latin.utils.ApplicationUtils;
/**
* Preference screen.
*/
public final class SpellCheckerSettingsFragment extends PreferenceFragment {
- /**
- * Empty constructor for fragment generation.
- */
- public SpellCheckerSettingsFragment() {
- }
-
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
+ public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
addPreferencesFromResource(R.xml.spell_checker_settings);
final PreferenceScreen preferenceScreen = getPreferenceScreen();
- if (preferenceScreen != null) {
- preferenceScreen.setTitle(ApplicationUtils.getAcitivityTitleResId(
- getActivity(), SpellCheckerSettingsActivity.class));
- }
+ preferenceScreen.setTitle(ApplicationUtils.getActivityTitleResId(
+ getActivity(), SpellCheckerSettingsActivity.class));
+ TwoStatePreferenceHelper.replaceCheckBoxPreferencesBySwitchPreferences(preferenceScreen);
}
}