aboutsummaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/res/layout/user_dictionary_add_word_fullscreen.xml20
-rw-r--r--java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java48
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java13
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java16
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java21
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java237
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsContentObserver.java110
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java48
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java55
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsManager.java160
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitator.java12
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java36
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java113
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java9
-rw-r--r--java/src/com/android/inputmethod/latin/NgramContext.java4
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java31
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java44
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/WordProperty.java28
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java8
-rw-r--r--java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java13
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java3
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java33
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java430
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java77
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java3
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java103
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java10
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java7
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java97
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java324
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java64
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java12
-rw-r--r--java/src/com/android/inputmethod/latin/utils/NetworkConnectivityUtils.java101
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ScriptUtils.java67
-rw-r--r--java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java23
35 files changed, 1055 insertions, 1325 deletions
diff --git a/java/res/layout/user_dictionary_add_word_fullscreen.xml b/java/res/layout/user_dictionary_add_word_fullscreen.xml
index 9bcb189b4..cbdfba67d 100644
--- a/java/res/layout/user_dictionary_add_word_fullscreen.xml
+++ b/java/res/layout/user_dictionary_add_word_fullscreen.xml
@@ -44,26 +44,6 @@
android:columnCount="2" >
<TextView
- android:id="@+id/user_dictionary_add_shortcut_label"
- style="?android:attr/textAppearanceSmall"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start|center_vertical"
- android:text="@string/user_dict_settings_add_shortcut_option_name" />
-
- <EditText
- android:id="@+id/user_dictionary_add_shortcut"
- android:layout_width="wrap_content"
- android:layout_gravity="fill_horizontal|center_vertical"
- android:layout_marginBottom="8dip"
- android:layout_marginStart="8dip"
- android:layout_marginTop="8dip"
- android:hint="@string/user_dict_settings_add_shortcut_hint"
- android:imeOptions="flagNoFullscreen"
- android:inputType="textNoSuggestions"
- android:maxLength="@integer/config_user_dictionary_max_word_length" />
-
- <TextView
android:id="@+id/user_dictionary_add_locale_label"
style="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
diff --git a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
deleted file mode 100644
index b78c357ab..000000000
--- a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2013 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.compat;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.provider.UserDictionary;
-
-import java.util.Locale;
-
-public final class UserDictionaryCompatUtils {
- @SuppressWarnings("deprecation")
- public static void addWord(final Context context, final String word,
- final int freq, final String shortcut, final Locale locale) {
- if (BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- addWordWithShortcut(context, word, freq, shortcut, locale);
- return;
- }
- // Fall back to the pre-JellyBean method.
- final Locale currentLocale = context.getResources().getConfiguration().locale;
- final int localeType = currentLocale.equals(locale)
- ? UserDictionary.Words.LOCALE_TYPE_CURRENT : UserDictionary.Words.LOCALE_TYPE_ALL;
- UserDictionary.Words.addWord(context, word, freq, localeType);
- }
-
- // {@link UserDictionary.Words#addWord(Context,String,int,String,Locale)} was introduced
- // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- private static void addWordWithShortcut(final Context context, final String word,
- final int freq, final String shortcut, final Locale locale) {
- UserDictionary.Words.addWord(context, word, freq, shortcut, locale);
- }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 46476e29e..fca5ecbc0 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -40,13 +40,11 @@ import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
-import com.android.inputmethod.latin.utils.NetworkConnectivityUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
-public final class KeyboardSwitcher implements KeyboardState.SwitchActions,
- NetworkConnectivityUtils.NetworkStateChangeListener {
+public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
private InputView mCurrentInputView;
@@ -412,15 +410,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions,
return mCurrentInputView;
}
- // {@link NetworkConnectivityUtils.NetworkStateChangeListener#onNetworkStateChanged(boolean)}.
- @Override
- public void onNetworkStateChanged() {
- if (mKeyboardView == null) {
- return;
- }
- mKeyboardView.updateShortcutKey(mRichImm.isShortcutImeReady());
- }
-
public int getKeyboardShiftMode() {
final Keyboard keyboard = getKeyboard();
if (keyboard == null) {
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index ab8b7515d..ce4bb7454 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -694,25 +694,25 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy
}
@Override
- public boolean onTouchEvent(final MotionEvent me) {
+ public boolean onTouchEvent(final MotionEvent event) {
if (getKeyboard() == null) {
return false;
}
if (mNonDistinctMultitouchHelper != null) {
- if (me.getPointerCount() > 1 && mTimerHandler.isInKeyRepeat()) {
+ if (event.getPointerCount() > 1 && mTimerHandler.isInKeyRepeat()) {
// Key repeating timer will be canceled if 2 or more keys are in action.
mTimerHandler.cancelKeyRepeatTimers();
}
// Non distinct multitouch screen support
- mNonDistinctMultitouchHelper.processMotionEvent(me, mKeyDetector);
+ mNonDistinctMultitouchHelper.processMotionEvent(event, mKeyDetector);
return true;
}
- return processMotionEvent(me);
+ return processMotionEvent(event);
}
- public boolean processMotionEvent(final MotionEvent me) {
- final int index = me.getActionIndex();
- final int id = me.getPointerId(index);
+ public boolean processMotionEvent(final MotionEvent event) {
+ final int index = event.getActionIndex();
+ final int id = event.getPointerId(index);
final PointerTracker tracker = PointerTracker.getPointerTracker(id);
// When a more keys panel is showing, we should ignore other fingers' single touch events
// other than the finger that is showing the more keys panel.
@@ -720,7 +720,7 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy
&& PointerTracker.getActivePointerTrackerCount() == 1) {
return true;
}
- tracker.processMotionEvent(me, mKeyDetector);
+ tracker.processMotionEvent(event, mKeyDetector);
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 7e4d66583..7ec964d83 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -74,7 +74,7 @@ public final class BinaryDictionary extends Dictionary {
private static final int FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX = 0;
private static final int FORMAT_WORD_PROPERTY_IS_POSSIBLY_OFFENSIVE_INDEX = 1;
private static final int FORMAT_WORD_PROPERTY_HAS_NGRAMS_INDEX = 2;
- private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3;
+ private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3; // DEPRECATED
private static final int FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX = 4;
// Format to get probability and historical info from native side via getWordPropertyNative().
@@ -410,11 +410,9 @@ public final class BinaryDictionary extends Dictionary {
outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX],
outFlags[FORMAT_WORD_PROPERTY_IS_POSSIBLY_OFFENSIVE_INDEX],
outFlags[FORMAT_WORD_PROPERTY_HAS_NGRAMS_INDEX],
- outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX],
outFlags[FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX], outProbabilityInfo,
outNgramPrevWordsArray, outNgramPrevWordIsBeginningOfSentenceArray,
- outNgramTargets, outNgramProbabilityInfo, outShortcutTargets,
- outShortcutProbabilities);
+ outNgramTargets, outNgramProbabilityInfo);
}
public static class GetNextWordPropertyResult {
@@ -442,19 +440,16 @@ public final class BinaryDictionary extends Dictionary {
}
// Add a unigram entry to binary dictionary with unigram attributes in native code.
- public boolean addUnigramEntry(final String word, final int probability,
- final String shortcutTarget, final int shortcutProbability,
- final boolean isBeginningOfSentence, final boolean isNotAWord,
- final boolean isPossiblyOffensive, final int timestamp) {
+ public boolean addUnigramEntry(
+ final String word, final int probability, final boolean isBeginningOfSentence,
+ final boolean isNotAWord, final boolean isPossiblyOffensive, final int timestamp) {
if (word == null || (word.isEmpty() && !isBeginningOfSentence)) {
return false;
}
final int[] codePoints = StringUtils.toCodePointArray(word);
- final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
- StringUtils.toCodePointArray(shortcutTarget) : null;
- if (!addUnigramEntryNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
- shortcutProbability, isBeginningOfSentence, isNotAWord, isPossiblyOffensive,
- timestamp)) {
+ if (!addUnigramEntryNative(mNativeDict, codePoints, probability,
+ null /* shortcutTargetCodePoints */, 0 /* shortcutProbability */,
+ isBeginningOfSentence, isNotAWord, isPossiblyOffensive, timestamp)) {
return false;
}
mHasUpdated = true;
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 22fd90795..ba0f9b807 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -16,23 +16,16 @@
package com.android.inputmethod.latin;
-import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.util.Log;
import com.android.inputmethod.annotations.ExternallyReferenced;
-import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.personalization.AccountUtils;
-import com.android.inputmethod.latin.utils.ExecutorUtils;
import java.io.File;
import java.util.ArrayList;
@@ -41,11 +34,8 @@ import java.util.Locale;
import javax.annotation.Nullable;
-public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
-
- private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME};
- private static final String[] PROJECTION_ID_ONLY = {BaseColumns._ID};
-
+public class ContactsBinaryDictionary extends ExpandableBinaryDictionary
+ implements ContactsChangedListener {
private static final String TAG = ContactsBinaryDictionary.class.getSimpleName();
private static final String NAME = "contacts";
@@ -53,35 +43,18 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private static final boolean DEBUG_DUMP = false;
/**
- * Frequency for contacts information into the dictionary
- */
- private static final int FREQUENCY_FOR_CONTACTS = 40;
- private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
-
- /** The maximum number of contacts that this dictionary supports. */
- private static final int MAX_CONTACT_COUNT = 10000;
-
- private static final int INDEX_NAME = 1;
-
- /** The number of contacts in the most recent dictionary rebuild. */
- private int mContactCountAtLastRebuild = 0;
-
- /** The hash code of ArrayList of contacts names in the most recent dictionary rebuild. */
- private int mHashCodeAtLastRebuild = 0;
-
- private ContentObserver mObserver;
-
- /**
* Whether to use "firstname lastname" in bigram predictions.
*/
private final boolean mUseFirstLastBigrams;
+ private final ContactsManager mContactsManager;
protected ContactsBinaryDictionary(final Context context, final Locale locale,
final File dictFile, final String name) {
super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_CONTACTS,
dictFile);
- mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
- registerObserver(context);
+ mUseFirstLastBigrams = ContactsDictionaryUtils.useFirstLastBigramsForLocale(locale);
+ mContactsManager = new ContactsManager(context);
+ mContactsManager.registerForUpdates(this /* listener */);
reloadDictionaryIfRequired();
}
@@ -92,34 +65,17 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
return new ContactsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME);
}
- private synchronized void registerObserver(final Context context) {
- if (mObserver != null) return;
- ContentResolver cres = context.getContentResolver();
- cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver =
- new ContentObserver(null) {
- @Override
- public void onChange(boolean self) {
- ExecutorUtils.getExecutor("Check Contacts").execute(new Runnable() {
- @Override
- public void run() {
- if (haveContentsChanged()) {
- setNeedsToRecreate();
- }
- }
- });
- }
- });
- }
-
@Override
public synchronized void close() {
- if (mObserver != null) {
- mContext.getContentResolver().unregisterContentObserver(mObserver);
- mObserver = null;
- }
+ mContactsManager.close();
super.close();
}
+ /**
+ * Typically called whenever the dictionary is created for the first time or
+ * recreated when we think that there are updates to the dictionary.
+ * This is called asynchronously.
+ */
@Override
public void loadInitialContentsLocked() {
loadDeviceAccountsEmailAddressesLocked();
@@ -128,6 +84,9 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
loadDictionaryForUriLocked(Contacts.CONTENT_URI);
}
+ /**
+ * Loads device accounts to the dictionary.
+ */
private void loadDeviceAccountsEmailAddressesLocked() {
final List<String> accountVocabulary =
AccountUtils.getDeviceAccountsEmailAddresses(mContext);
@@ -139,80 +98,25 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
Log.d(TAG, "loadAccountVocabulary: " + word);
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addUnigramLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */,
- 0 /* shortcutFreq */, false /* isNotAWord */, false /* isPossiblyOffensive */,
+ addUnigramLocked(word, ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS,
+ false /* isNotAWord */, false /* isPossiblyOffensive */,
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
}
+ /**
+ * Loads data within content providers to the dictionary.
+ */
private void loadDictionaryForUriLocked(final Uri uri) {
- Cursor cursor = null;
- try {
- cursor = mContext.getContentResolver().query(uri, PROJECTION, null, null, null);
- if (null == cursor) {
- return;
- }
- if (cursor.moveToFirst()) {
- mContactCountAtLastRebuild = getContactCount();
- addWordsLocked(cursor);
- }
- } catch (final SQLiteException e) {
- Log.e(TAG, "SQLiteException in the remote Contacts process.", e);
- } catch (final IllegalStateException e) {
- Log.e(TAG, "Contacts DB is having problems", e);
- } finally {
- if (null != cursor) {
- cursor.close();
- }
+ final ArrayList<String> validNames = mContactsManager.getValidNames(uri);
+ for (final String name : validNames) {
+ addNameLocked(name);
}
- }
-
- private static boolean useFirstLastBigramsForLocale(final Locale locale) {
- // TODO: Add firstname/lastname bigram rules for other languages.
- if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
- return true;
+ if (uri.equals(Contacts.CONTENT_URI)) {
+ // Since we were able to add content successfully, update the local
+ // state of the manager.
+ mContactsManager.updateLocalState(validNames);
}
- return false;
- }
-
- private void addWordsLocked(final Cursor cursor) {
- int count = 0;
- final ArrayList<String> names = new ArrayList<>();
- while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
- String name = cursor.getString(INDEX_NAME);
- if (isValidName(name)) {
- names.add(name);
- addNameLocked(name);
- ++count;
- } else {
- if (DEBUG_DUMP) {
- Log.d(TAG, "Invalid name: " + name);
- }
- }
- cursor.moveToNext();
- }
- mHashCodeAtLastRebuild = names.hashCode();
- }
-
- private int getContactCount() {
- // TODO: consider switching to a rawQuery("select count(*)...") on the database if
- // performance is a bottleneck.
- Cursor cursor = null;
- try {
- cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI, PROJECTION_ID_ONLY,
- null, null, null);
- if (null == cursor) {
- return 0;
- }
- return cursor.getCount();
- } catch (final SQLiteException e) {
- Log.e(TAG, "SQLiteException in the remote Contacts process.", e);
- } finally {
- if (null != cursor) {
- cursor.close();
- }
- }
- return 0;
}
/**
@@ -225,7 +129,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
// TODO: Better tokenization for non-Latin writing systems
for (int i = 0; i < len; i++) {
if (Character.isLetter(name.codePointAt(i))) {
- int end = getWordEndPosition(name, len, i);
+ int end = ContactsDictionaryUtils.getWordEndPosition(name, len, i);
String word = name.substring(i, end);
if (DEBUG_DUMP) {
Log.d(TAG, "addName word = " + word);
@@ -239,13 +143,15 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
Log.d(TAG, "addName " + name + ", " + word + ", " + ngramContext);
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addUnigramLocked(word, FREQUENCY_FOR_CONTACTS,
- null /* shortcut */, 0 /* shortcutFreq */, false /* isNotAWord */,
+ addUnigramLocked(word,
+ ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS, false /* isNotAWord */,
false /* isPossiblyOffensive */,
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
- if (!ngramContext.isValid() && mUseFirstLastBigrams) {
+ if (ngramContext.isValid() && mUseFirstLastBigrams) {
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addNgramEntryLocked(ngramContext, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
+ addNgramEntryLocked(ngramContext,
+ word,
+ ContactsDictionaryConstants.FREQUENCY_FOR_CONTACTS_BIGRAM,
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
ngramContext = ngramContext.getNextNgramContext(
@@ -255,75 +161,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
}
- /**
- * Returns the index of the last letter in the word, starting from position startIndex.
- */
- private static int getWordEndPosition(final String string, final int len,
- final int startIndex) {
- int end;
- int cp = 0;
- for (end = startIndex + 1; end < len; end += Character.charCount(cp)) {
- cp = string.codePointAt(end);
- if (!(cp == Constants.CODE_DASH || cp == Constants.CODE_SINGLE_QUOTE
- || Character.isLetter(cp))) {
- break;
- }
- }
- return end;
- }
-
- boolean haveContentsChanged() {
- final long startTime = SystemClock.uptimeMillis();
- final int contactCount = getContactCount();
- if (contactCount > MAX_CONTACT_COUNT) {
- // If there are too many contacts then return false. In this rare case it is impossible
- // to include all of them anyways and the cost of rebuilding the dictionary is too high.
- // TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts?
- return false;
- }
- if (contactCount != mContactCountAtLastRebuild) {
- if (DEBUG) {
- Log.d(TAG, "Contact count changed: " + mContactCountAtLastRebuild + " to "
- + contactCount);
- }
- return true;
- }
- // Check all contacts since it's not possible to find out which names have changed.
- // This is needed because it's possible to receive extraneous onChange events even when no
- // name has changed.
- final Cursor cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI, PROJECTION,
- null, null, null);
- if (null == cursor) {
- return false;
- }
- final ArrayList<String> names = new ArrayList<>();
- try {
- if (cursor.moveToFirst()) {
- while (!cursor.isAfterLast()) {
- String name = cursor.getString(INDEX_NAME);
- if (isValidName(name)) {
- names.add(name);
- }
- cursor.moveToNext();
- }
- }
- if (names.hashCode() != mHashCodeAtLastRebuild) {
- return true;
- }
- } finally {
- cursor.close();
- }
- if (DEBUG) {
- Log.d(TAG, "No contacts changed. (runtime = " + (SystemClock.uptimeMillis() - startTime)
- + " ms)");
- }
- return false;
- }
-
- private static boolean isValidName(final String name) {
- if (name != null && -1 == name.indexOf(Constants.CODE_COMMERCIAL_AT)) {
- return true;
- }
- return false;
+ @Override
+ public void onContactsChange() {
+ setNeedsToRecreate();
}
}
diff --git a/java/src/com/android/inputmethod/latin/ContactsContentObserver.java b/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
new file mode 100644
index 000000000..019d17d56
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.SystemClock;
+import android.provider.ContactsContract.Contacts;
+import android.util.Log;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * A content observer that listens to updates to content provider {@link Contacts.CONTENT_URI}.
+ */
+// TODO:add test
+public class ContactsContentObserver {
+ private static final String TAG = ContactsContentObserver.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private ContentObserver mObserver;
+
+ private final Context mContext;
+ private final ContactsManager mManager;
+
+ public ContactsContentObserver(final ContactsManager manager, final Context context) {
+ mManager = manager;
+ mContext = context;
+ }
+
+ public void registerObserver(final ContactsChangedListener listener) {
+ if (DEBUG) {
+ Log.d(TAG, "Registered Contacts Content Observer");
+ }
+ mObserver = new ContentObserver(null /* handler */) {
+ @Override
+ public void onChange(boolean self) {
+ getBgExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ if (haveContentsChanged()) {
+ if (DEBUG) {
+ Log.d(TAG, "Contacts have changed; notifying listeners");
+ }
+ listener.onContactsChange();
+ }
+ }
+ });
+ }
+ };
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ contentResolver.registerContentObserver(Contacts.CONTENT_URI, true, mObserver);
+ }
+
+ @UsedForTesting
+ private ExecutorService getBgExecutor() {
+ return ExecutorUtils.getExecutor("Check Contacts");
+ }
+
+ private boolean haveContentsChanged() {
+ final long startTime = SystemClock.uptimeMillis();
+ final int contactCount = mManager.getContactCount();
+ if (contactCount > ContactsDictionaryConstants.MAX_CONTACT_COUNT) {
+ // If there are too many contacts then return false. In this rare case it is impossible
+ // to include all of them anyways and the cost of rebuilding the dictionary is too high.
+ // TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts?
+ return false;
+ }
+ if (contactCount != mManager.getContactCountAtLastRebuild()) {
+ if (DEBUG) {
+ Log.d(TAG, "Contact count changed: " + mManager.getContactCountAtLastRebuild()
+ + " to " + contactCount);
+ }
+ return true;
+ }
+ final ArrayList<String> names = mManager.getValidNames(Contacts.CONTENT_URI);
+ if (names.hashCode() != mManager.getHashCodeAtLastRebuild()) {
+ return true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "No contacts changed. (runtime = " + (SystemClock.uptimeMillis() - startTime)
+ + " ms)");
+ }
+ return false;
+ }
+
+ public void unregister() {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java b/java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java
new file mode 100644
index 000000000..8d8faca58
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionaryConstants.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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.provider.BaseColumns;
+import android.provider.ContactsContract.Contacts;
+
+/**
+ * Constants related to Contacts Content Provider.
+ */
+public class ContactsDictionaryConstants {
+ /**
+ * Projections for {@link Contacts.CONTENT_URI}
+ */
+ public static final String[] PROJECTION = { BaseColumns._ID, Contacts.DISPLAY_NAME };
+ public static final String[] PROJECTION_ID_ONLY = { BaseColumns._ID };
+
+ /**
+ * Frequency for contacts information into the dictionary
+ */
+ public static final int FREQUENCY_FOR_CONTACTS = 40;
+ public static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
+
+ /**
+ * The maximum number of contacts that this dictionary supports.
+ */
+ public static final int MAX_CONTACT_COUNT = 10000;
+
+ /**
+ * Index of the column for 'name' in content providers:
+ * Contacts & ContactsContract.Profile.
+ */
+ public static final int NAME_INDEX = 1;
+}
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java b/java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java
new file mode 100644
index 000000000..b77388434
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionaryUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import com.android.inputmethod.latin.common.Constants;
+
+import java.util.Locale;
+
+/**
+ * Utility methods related contacts dictionary.
+ */
+public class ContactsDictionaryUtils {
+
+ /**
+ * Returns the index of the last letter in the word, starting from position startIndex.
+ */
+ public static int getWordEndPosition(final String string, final int len,
+ final int startIndex) {
+ int end;
+ int cp = 0;
+ for (end = startIndex + 1; end < len; end += Character.charCount(cp)) {
+ cp = string.codePointAt(end);
+ if (cp != Constants.CODE_DASH && cp != Constants.CODE_SINGLE_QUOTE
+ && !Character.isLetter(cp)) {
+ break;
+ }
+ }
+ return end;
+ }
+
+ /**
+ * Returns true if the locale supports using first name and last name as bigrams.
+ */
+ public static boolean useFirstLastBigramsForLocale(final Locale locale) {
+ // TODO: Add firstname/lastname bigram rules for other languages.
+ if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/ContactsManager.java b/java/src/com/android/inputmethod/latin/ContactsManager.java
new file mode 100644
index 000000000..dc5abd955
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ContactsManager.java
@@ -0,0 +1,160 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.util.Log;
+
+import com.android.inputmethod.latin.common.Constants;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages all interactions with Contacts DB.
+ *
+ * The manager provides an API for listening to meaning full updates by keeping a
+ * measure of the current state of the content provider.
+ */
+// TODO:Add test
+public class ContactsManager {
+ private static final String TAG = ContactsManager.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /**
+ * Interface to implement for classes interested in getting notified for updates
+ * to Contacts content provider.
+ */
+ public static interface ContactsChangedListener {
+ public void onContactsChange();
+ }
+
+ /**
+ * The number of contacts observed in the most recent instance of
+ * contacts content provider.
+ */
+ private AtomicInteger mContactCountAtLastRebuild = new AtomicInteger(0);
+
+ /**
+ * The hash code of list of valid contacts names in the most recent dictionary
+ * rebuild.
+ */
+ private AtomicInteger mHashCodeAtLastRebuild = new AtomicInteger(0);
+
+ private final Context mContext;
+ private final ContactsContentObserver mObserver;
+
+ public ContactsManager(final Context context) {
+ mContext = context;
+ mObserver = new ContactsContentObserver(this /* ContactsManager */, context);
+ }
+
+ // TODO: This was synchronized in previous version. Why?
+ public void registerForUpdates(final ContactsChangedListener listener) {
+ mObserver.registerObserver(listener);
+ }
+
+ public int getContactCountAtLastRebuild() {
+ return mContactCountAtLastRebuild.get();
+ }
+
+ public int getHashCodeAtLastRebuild() {
+ return mHashCodeAtLastRebuild.get();
+ }
+
+ /**
+ * Returns all the valid names in the Contacts DB. Callers should also
+ * call {@link #updateLocalState(ArrayList)} after they are done with result
+ * so that the manager can cache local state for determining updates.
+ */
+ public ArrayList<String> getValidNames(final Uri uri) {
+ final ArrayList<String> names = new ArrayList<>();
+ // Check all contacts since it's not possible to find out which names have changed.
+ // This is needed because it's possible to receive extraneous onChange events even when no
+ // name has changed.
+ final Cursor cursor = mContext.getContentResolver().query(uri,
+ ContactsDictionaryConstants.PROJECTION, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ while (!cursor.isAfterLast()) {
+ final String name = cursor.getString(
+ ContactsDictionaryConstants.NAME_INDEX);
+ if (isValidName(name)) {
+ names.add(name);
+ }
+ cursor.moveToNext();
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return names;
+ }
+
+ /**
+ * Returns the number of contacts in contacts content provider.
+ */
+ public int getContactCount() {
+ // TODO: consider switching to a rawQuery("select count(*)...") on the database if
+ // performance is a bottleneck.
+ Cursor cursor = null;
+ try {
+ cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI,
+ ContactsDictionaryConstants.PROJECTION_ID_ONLY, null, null, null);
+ if (null == cursor) {
+ return 0;
+ }
+ return cursor.getCount();
+ } catch (final SQLiteException e) {
+ Log.e(TAG, "SQLiteException in the remote Contacts process.", e);
+ } finally {
+ if (null != cursor) {
+ cursor.close();
+ }
+ }
+ return 0;
+ }
+
+ private static boolean isValidName(final String name) {
+ if (name != null && -1 == name.indexOf(Constants.CODE_COMMERCIAL_AT)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Updates the local state of the manager. This should be called when the callers
+ * are done with all the updates of the content provider successfully.
+ */
+ public void updateLocalState(final ArrayList<String> names) {
+ mContactCountAtLastRebuild.set(getContactCount());
+ mHashCodeAtLastRebuild.set(names.hashCode());
+ }
+
+ /**
+ * Performs any necessary cleanup.
+ */
+ public void close() {
+ mObserver.unregister();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 3e4cda47a..c22dc287c 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.util.Pair;
-import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.KeyboardLayout;
@@ -28,7 +27,6 @@ import com.android.inputmethod.latin.utils.SuggestionResults;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -90,8 +88,6 @@ public interface DictionaryFacilitator {
void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
}
- void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
-
// TODO: remove this, it's confusing with seamless multiple language switching
void setIsMonolingualUser(final boolean isMonolingualUser);
@@ -175,4 +171,12 @@ public interface DictionaryFacilitator {
void dumpDictionaryForDebug(final String dictName);
ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts();
+
+ void addOrIncrementTerm(String fileName,
+ String finalWordToBeAdded,
+ NgramContext ngramContext,
+ int increment,
+ int timeStampInSeconds);
+
+ void clearLanguageModel(String filePath);
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
index dd34faef8..3d76751ce 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.KeyboardLayout;
@@ -29,9 +28,6 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-import com.android.inputmethod.latin.utils.DistracterFilter;
-import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions;
-import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
@@ -42,7 +38,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -76,7 +71,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
// To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
private final Object mLock = new Object();
- private final DistracterFilter mDistracterFilter;
public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
DICT_TYPE_TO_CLASS = new HashMap<>();
@@ -233,15 +227,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
public DictionaryFacilitatorImpl() {
- mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
- }
-
- public DictionaryFacilitatorImpl(final Context context) {
- mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context);
- }
-
- public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
- mDistracterFilter.updateEnabledSubtypes(enabledSubtypes);
}
// TODO: remove this, it's confusing with seamless multiple language switching
@@ -545,7 +530,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
dictionaryGroup.closeDict(dictType);
}
}
- mDistracterFilter.close();
}
@UsedForTesting
@@ -659,9 +643,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
// We don't add words with 0-frequency (assuming they would be profanity etc.).
final boolean isValid = maxFreq > 0;
UserHistoryDictionary.addToDictionary(userHistoryDictionary, ngramContext, secondWord,
- isValid, timeStampInSeconds,
- new DistracterFilterCheckingIsInDictionary(
- mDistracterFilter, userHistoryDictionary));
+ isValid, timeStampInSeconds);
}
private void removeWord(final String dictName, final String word) {
@@ -764,10 +746,12 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
return maxFreq;
}
+ @Override
public int getFrequency(final String word) {
return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */);
}
+ @Override
public int getMaxFrequencyOfExactMatches(final String word) {
return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */);
}
@@ -811,4 +795,18 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
return statsOfEnabledSubDicts;
}
+
+ @Override
+ public void addOrIncrementTerm(String fileName,
+ String word,
+ NgramContext ngramContext,
+ int increment,
+ int timeStampInSeconds) {
+ // Do nothing.
+ }
+
+ @Override
+ public void clearLanguageModel(String filePath) {
+ // Do nothing.
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 87d46e226..8c780027b 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -31,7 +31,6 @@ import com.android.inputmethod.latin.makedict.WordProperty;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.CombinedFormatUtils;
-import com.android.inputmethod.latin.utils.DistracterFilter;
import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.WordInputEventForPersonalization;
@@ -40,7 +39,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
-import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -57,7 +55,6 @@ import javax.annotation.Nullable;
*
* A class that extends this abstract class must have a static factory method named
* getDictionary(Context context, Locale locale, File dictFile, String dictNamePrefix)
- * @see DictionaryFacilitator#getSubDict(String,Context,Locale,File,String)
*/
abstract public class ExpandableBinaryDictionary extends Dictionary {
private static final boolean DEBUG = false;
@@ -172,33 +169,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private static void asyncExecuteTaskWithLock(final Lock lock, final String executorName,
final Runnable task) {
- asyncPreCheckAndExecuteTaskWithLock(lock, null /* preCheckTask */, executorName, task);
- }
-
- private void asyncPreCheckAndExecuteTaskWithWriteLock(
- final Callable<Boolean> preCheckTask, final Runnable task) {
- asyncPreCheckAndExecuteTaskWithLock(mLock.writeLock(), preCheckTask,
- mDictName /* executorName */, task);
-
- }
-
- // Execute task with lock when the result of preCheckTask is true or preCheckTask is null.
- private static void asyncPreCheckAndExecuteTaskWithLock(final Lock lock,
- final Callable<Boolean> preCheckTask, final String executorName, final Runnable task) {
- final String tag = TAG;
ExecutorUtils.getExecutor(executorName).execute(new Runnable() {
@Override
public void run() {
- if (preCheckTask != null) {
- try {
- if (!preCheckTask.call().booleanValue()) {
- return;
- }
- } catch (final Exception e) {
- Log.e(tag, "The pre check task throws an exception.", e);
- return;
- }
- }
lock.lock();
try {
task.run();
@@ -305,17 +278,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
}
- private void updateDictionaryWithWriteLockIfWordIsNotADistracter(
- @Nonnull final Runnable updateTask,
- @Nonnull final String word, @Nonnull final DistracterFilter distracterFilter) {
+ private void updateDictionaryWithWriteLock(@Nonnull final Runnable updateTask) {
reloadDictionaryIfRequired();
- final Callable<Boolean> preCheckTask = new Callable<Boolean>() {
- @Override
- public Boolean call() throws Exception {
- return !distracterFilter.isDistracterToWordsInDictionaries(
- NgramContext.EMPTY_PREV_WORDS_INFO, word, mLocale);
- }
- };
final Runnable task = new Runnable() {
@Override
public void run() {
@@ -326,29 +290,25 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
updateTask.run();
}
};
- asyncPreCheckAndExecuteTaskWithWriteLock(preCheckTask, task);
+ asyncExecuteTaskWithWriteLock(task);
}
/**
* Adds unigram information of a word to the dictionary. May overwrite an existing entry.
*/
- public void addUnigramEntryWithCheckingDistracter(final String word, final int frequency,
- final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
- final boolean isPossiblyOffensive, final int timestamp,
- @Nonnull final DistracterFilter distracterFilter) {
- updateDictionaryWithWriteLockIfWordIsNotADistracter(new Runnable() {
+ public void addUnigramEntry(final String word, final int frequency,
+ final boolean isNotAWord, final boolean isPossiblyOffensive, final int timestamp) {
+ updateDictionaryWithWriteLock(new Runnable() {
@Override
public void run() {
- addUnigramLocked(word, frequency, shortcutTarget, shortcutFreq,
- isNotAWord, isPossiblyOffensive, timestamp);
+ addUnigramLocked(word, frequency, isNotAWord, isPossiblyOffensive, timestamp);
}
- }, word, distracterFilter);
+ });
}
protected void addUnigramLocked(final String word, final int frequency,
- final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
- final boolean isPossiblyOffensive, final int timestamp) {
- if (!mBinaryDictionary.addUnigramEntry(word, frequency, shortcutTarget, shortcutFreq,
+ final boolean isNotAWord, final boolean isPossiblyOffensive, final int timestamp) {
+ if (!mBinaryDictionary.addUnigramEntry(word, frequency,
false /* isBeginningOfSentence */, isNotAWord, isPossiblyOffensive, timestamp)) {
Log.e(TAG, "Cannot add unigram entry. word: " + word);
}
@@ -405,37 +365,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
/**
- * Dynamically remove the n-gram entry in the dictionary.
- */
- @UsedForTesting
- public void removeNgramDynamically(@Nonnull final NgramContext ngramContext,
- final String word) {
- reloadDictionaryIfRequired();
- asyncExecuteTaskWithWriteLock(new Runnable() {
- @Override
- public void run() {
- final BinaryDictionary binaryDictionary = getBinaryDictionary();
- if (binaryDictionary == null) {
- return;
- }
- runGCIfRequiredLocked(true /* mindsBlockByGC */);
- if (!binaryDictionary.removeNgramEntry(ngramContext, word)) {
- if (DEBUG) {
- Log.i(TAG, "Cannot remove n-gram entry.");
- Log.i(TAG, " NgramContext: " + ngramContext + ", word: " + word);
- }
- }
- }
- });
- }
-
- /**
- * Update dictionary for the word with the ngramContext if the word is not a distracter.
+ * Update dictionary for the word with the ngramContext.
*/
- public void updateEntriesForWordWithCheckingDistracter(@Nonnull final NgramContext ngramContext,
- final String word, final boolean isValidWord, final int count, final int timestamp,
- @Nonnull final DistracterFilter distracterFilter) {
- updateDictionaryWithWriteLockIfWordIsNotADistracter(new Runnable() {
+ public void updateEntriesForWord(@Nonnull final NgramContext ngramContext,
+ final String word, final boolean isValidWord, final int count, final int timestamp) {
+ updateDictionaryWithWriteLock(new Runnable() {
@Override
public void run() {
final BinaryDictionary binaryDictionary = getBinaryDictionary();
@@ -446,20 +380,29 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
isValidWord, count, timestamp)) {
if (DEBUG) {
Log.e(TAG, "Cannot update counter. word: " + word
- + " context: "+ ngramContext.toString());
+ + " context: " + ngramContext.toString());
}
}
}
- }, word, distracterFilter);
+ });
}
+ /**
+ * Used by Sketch.
+ * {@see https://cs.corp.google.com/#android/vendor/unbundled_google/packages/LatinIMEGoogle/tools/sketch/ime-simulator/src/com/android/inputmethod/sketch/imesimulator/ImeSimulator.java&q=updateEntriesForInputEventsCallback&l=286}
+ */
+ @UsedForTesting
public interface UpdateEntriesForInputEventsCallback {
public void onFinished();
}
/**
* Dynamically update entries according to input events.
+ *
+ * Used by Sketch.
+ * {@see https://cs.corp.google.com/#android/vendor/unbundled_google/packages/LatinIMEGoogle/tools/sketch/ime-simulator/src/com/android/inputmethod/sketch/imesimulator/ImeSimulator.java&q=updateEntriesForInputEventsCallback&l=286}
*/
+ @UsedForTesting
public void updateEntriesForInputEvents(
@Nonnull final ArrayList<WordInputEventForPersonalization> inputEvents,
final UpdateEntriesForInputEventsCallback callback) {
@@ -571,11 +514,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
- protected boolean isValidNgramLocked(final NgramContext ngramContext, final String word) {
- if (mBinaryDictionary == null) return false;
- return mBinaryDictionary.isValidNgram(ngramContext, word);
- }
-
/**
* Loads the current binary dictionary from internal storage. Assumes the dictionary file
* exists.
@@ -589,6 +527,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
Thread.sleep(15000);
Log.w(TAG, "End stress in loading");
} catch (InterruptedException e) {
+ Log.w("Interrupted while loading: " + mDictName, e);
}
}
final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
@@ -653,7 +592,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/**
* Reloads the dictionary. Access is controlled on a per dictionary file basis.
*/
- private final void asyncReloadDictionary() {
+ private void asyncReloadDictionary() {
final AtomicBoolean isReloading = mIsReloading;
if (!isReloading.compareAndSet(false, true)) {
return;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 74ef6481a..a1ab5c090 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -87,7 +87,6 @@ import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
-import com.android.inputmethod.latin.utils.NetworkConnectivityUtils;
import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.StatsUtilsManager;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
@@ -128,7 +127,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Settings mSettings;
private final DictionaryFacilitator mDictionaryFacilitator =
- DictionaryFacilitatorProvider.newDictionaryFacilitator(this /* context */);
+ DictionaryFacilitatorProvider.newDictionaryFacilitator();
final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
this /* SuggestionStripViewAccessor */, mDictionaryFacilitator);
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
@@ -565,8 +564,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
loadSettings();
resetDictionaryFacilitatorIfNecessary();
- NetworkConnectivityUtils.onCreate(this /* context */, mKeyboardSwitcher /* listener */);
-
// Register to receive ringer mode change.
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
@@ -608,8 +605,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!mHandler.hasPendingReopenDictionaries()) {
resetDictionaryFacilitator(locales);
}
- mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList(
- true /* allowsImplicitlySelectedSubtypes */));
refreshPersonalizationDictionarySession(currentSettingsValues);
resetDictionaryFacilitatorIfNecessary();
mStatsUtilsManager.onLoadSettings(this /* context */, currentSettingsValues,
@@ -705,7 +700,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onDestroy() {
mDictionaryFacilitator.closeDictionaries();
mSettings.onDestroy();
- NetworkConnectivityUtils.onDestroy(this /* context */);
unregisterReceiver(mRingerModeChangeReceiver);
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
@@ -719,7 +713,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
unregisterReceiver(mRingerModeChangeReceiver);
- NetworkConnectivityUtils.onDestroy(this /* context */);
mInputLogic.recycle();
}
diff --git a/java/src/com/android/inputmethod/latin/NgramContext.java b/java/src/com/android/inputmethod/latin/NgramContext.java
index 86155e0be..53bec6e59 100644
--- a/java/src/com/android/inputmethod/latin/NgramContext.java
+++ b/java/src/com/android/inputmethod/latin/NgramContext.java
@@ -108,7 +108,9 @@ public class NgramContext {
mPrevWordsCount = prevWordsInfo.length;
}
- // Create next prevWordsInfo using current prevWordsInfo.
+ /**
+ * Create next prevWordsInfo using current prevWordsInfo.
+ */
@Nonnull
public NgramContext getNextNgramContext(final WordInfo wordInfo) {
final int nextPrevWordCount = Math.min(
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 64a7cf347..c8e0b93bf 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -17,7 +17,6 @@
package com.android.inputmethod.latin;
import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
-import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY;
import android.content.Context;
import android.content.SharedPreferences;
@@ -36,7 +35,6 @@ import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
-import com.android.inputmethod.latin.utils.NetworkConnectivityUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Collections;
@@ -288,24 +286,20 @@ public class RichInputMethodManager {
}
public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) {
- return checkIfSubtypeBelongsToImeAndEnabled(getInputMethodInfoOfThisIme(), subtype);
+ return checkIfSubtypeBelongsToList(subtype,
+ getEnabledInputMethodSubtypeList(
+ getInputMethodInfoOfThisIme(),
+ true /* allowsImplicitlySelectedSubtypes */));
}
public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
final InputMethodSubtype subtype) {
final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype);
- final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(
- subtype, getMyEnabledInputMethodSubtypeList(
- false /* allowsImplicitlySelectedSubtypes */));
+ final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList(subtype,
+ getMyEnabledInputMethodSubtypeList(false /* allowsImplicitlySelectedSubtypes */));
return subtypeEnabled && !subtypeExplicitlyEnabled;
}
- public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
- final InputMethodSubtype subtype) {
- return checkIfSubtypeBelongsToList(subtype, getEnabledInputMethodSubtypeList(imi,
- true /* allowsImplicitlySelectedSubtypes */));
- }
-
private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
final List<InputMethodSubtype> subtypes) {
return getSubtypeIndexInList(subtype, subtypes) != INDEX_NOT_FOUND;
@@ -564,16 +558,6 @@ public class RichInputMethodManager {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
- public boolean isShortcutImeEnabled() {
- if (mShortcutInputMethodInfo == null) {
- return false;
- }
- if (mShortcutSubtype == null) {
- return true;
- }
- return checkIfSubtypeBelongsToImeAndEnabled(mShortcutInputMethodInfo, mShortcutSubtype);
- }
-
public boolean isShortcutImeReady() {
if (mShortcutInputMethodInfo == null) {
return false;
@@ -581,9 +565,6 @@ public class RichInputMethodManager {
if (mShortcutSubtype == null) {
return true;
}
- if (mShortcutSubtype.containsExtraValueKey(REQ_NETWORK_CONNECTIVITY)) {
- return NetworkConnectivityUtils.isNetworkConnected();
- }
return true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 1ed210377..fe24ccfc2 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -22,7 +22,6 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
-import android.os.Build;
import android.provider.UserDictionary.Words;
import android.text.TextUtils;
import android.util.Log;
@@ -47,19 +46,8 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
private static final String USER_DICTIONARY_ALL_LANGUAGES = "";
private static final int HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY = 250;
private static final int LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY = 160;
- // Shortcut frequency is 0~15, with 15 = whitelist. We don't want user dictionary entries
- // to auto-correct, so we set this to the highest frequency that won't, i.e. 14.
- private static final int USER_DICT_SHORTCUT_FREQUENCY = 14;
-
- private static final String[] PROJECTION_QUERY_WITH_SHORTCUT = new String[] {
- Words.WORD,
- Words.SHORTCUT,
- Words.FREQUENCY,
- };
- private static final String[] PROJECTION_QUERY_WITHOUT_SHORTCUT = new String[] {
- Words.WORD,
- Words.FREQUENCY,
- };
+
+ private static final String[] PROJECTION_QUERY = new String[] {Words.WORD, Words.FREQUENCY};
private static final String NAME = "userunigram";
@@ -171,20 +159,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
requestArguments = localeElements;
}
final String requestString = request.toString();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- try {
- addWordsFromProjectionLocked(PROJECTION_QUERY_WITH_SHORTCUT, requestString,
- requestArguments);
- } catch (IllegalArgumentException e) {
- // This may happen on some non-compliant devices where the declared API is JB+ but
- // the SHORTCUT column is not present for some reason.
- addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
- requestArguments);
- }
- } else {
- addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
- requestArguments);
- }
+ addWordsFromProjectionLocked(PROJECTION_QUERY, requestString, requestArguments);
}
private void addWordsFromProjectionLocked(final String[] query, String request,
@@ -219,31 +194,20 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
}
private void addWordsLocked(final Cursor cursor) {
- final boolean hasShortcutColumn = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
if (cursor == null) return;
if (cursor.moveToFirst()) {
final int indexWord = cursor.getColumnIndex(Words.WORD);
- final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(Words.SHORTCUT) : 0;
final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
while (!cursor.isAfterLast()) {
final String word = cursor.getString(indexWord);
- final String shortcut = hasShortcutColumn ? cursor.getString(indexShortcut) : null;
final int frequency = cursor.getInt(indexFrequency);
final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency);
// Safeguard against adding really long words.
if (word.length() <= MAX_WORD_LENGTH) {
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addUnigramLocked(word, adjustedFrequency, null /* shortcutTarget */,
- 0 /* shortcutFreq */, false /* isNotAWord */,
+ addUnigramLocked(word, adjustedFrequency, false /* isNotAWord */,
false /* isPossiblyOffensive */,
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
- if (null != shortcut && shortcut.length() <= MAX_WORD_LENGTH) {
- runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addUnigramLocked(shortcut, adjustedFrequency, word,
- USER_DICT_SHORTCUT_FREQUENCY, true /* isNotAWord */,
- false /* isPossiblyOffensive */,
- BinaryDictionary.NOT_A_VALID_TIMESTAMP);
- }
}
cursor.moveToNext();
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
index 388d57816..264e75710 100644
--- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
+++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
@@ -37,13 +37,11 @@ import javax.annotation.Nullable;
public final class WordProperty implements Comparable<WordProperty> {
public final String mWord;
public final ProbabilityInfo mProbabilityInfo;
- public final ArrayList<WeightedString> mShortcutTargets;
public final ArrayList<NgramProperty> mNgrams;
// TODO: Support mIsBeginningOfSentence.
public final boolean mIsBeginningOfSentence;
public final boolean mIsNotAWord;
public final boolean mIsPossiblyOffensive;
- public final boolean mHasShortcuts;
public final boolean mHasNgrams;
private int mHashCode = 0;
@@ -51,12 +49,10 @@ public final class WordProperty implements Comparable<WordProperty> {
// TODO: Support n-gram.
@UsedForTesting
public WordProperty(final String word, final ProbabilityInfo probabilityInfo,
- final ArrayList<WeightedString> shortcutTargets,
@Nullable final ArrayList<WeightedString> bigrams,
final boolean isNotAWord, final boolean isPossiblyOffensive) {
mWord = word;
mProbabilityInfo = probabilityInfo;
- mShortcutTargets = shortcutTargets;
if (null == bigrams) {
mNgrams = null;
} else {
@@ -70,7 +66,6 @@ public final class WordProperty implements Comparable<WordProperty> {
mIsNotAWord = isNotAWord;
mIsPossiblyOffensive = isPossiblyOffensive;
mHasNgrams = bigrams != null && !bigrams.isEmpty();
- mHasShortcuts = shortcutTargets != null && !shortcutTargets.isEmpty();
}
private static ProbabilityInfo createProbabilityInfoFromArray(final int[] probabilityInfo) {
@@ -84,21 +79,17 @@ public final class WordProperty implements Comparable<WordProperty> {
// Construct word property using information from native code.
// This represents invalid word when the probability is BinaryDictionary.NOT_A_PROBABILITY.
public WordProperty(final int[] codePoints, final boolean isNotAWord,
- final boolean isPossiblyOffensive, final boolean hasBigram, final boolean hasShortcuts,
+ final boolean isPossiblyOffensive, final boolean hasBigram,
final boolean isBeginningOfSentence, final int[] probabilityInfo,
final ArrayList<int[][]> ngramPrevWordsArray,
final ArrayList<boolean[]> ngramPrevWordIsBeginningOfSentenceArray,
- final ArrayList<int[]> ngramTargets, final ArrayList<int[]> ngramProbabilityInfo,
- final ArrayList<int[]> shortcutTargets,
- final ArrayList<Integer> shortcutProbabilities) {
+ final ArrayList<int[]> ngramTargets, final ArrayList<int[]> ngramProbabilityInfo) {
mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo);
- mShortcutTargets = new ArrayList<>();
final ArrayList<NgramProperty> ngrams = new ArrayList<>();
mIsBeginningOfSentence = isBeginningOfSentence;
mIsNotAWord = isNotAWord;
mIsPossiblyOffensive = isPossiblyOffensive;
- mHasShortcuts = hasShortcuts;
mHasNgrams = hasBigram;
final int relatedNgramCount = ngramTargets.size();
@@ -121,14 +112,6 @@ public final class WordProperty implements Comparable<WordProperty> {
ngrams.add(new NgramProperty(ngramTarget, ngramContext));
}
mNgrams = ngrams.isEmpty() ? null : ngrams;
-
- final int shortcutTargetCount = shortcutTargets.size();
- for (int i = 0; i < shortcutTargetCount; i++) {
- final String shortcutTargetString =
- StringUtils.getStringFromNullTerminatedCodePointArray(shortcutTargets.get(i));
- mShortcutTargets.add(
- new WeightedString(shortcutTargetString, shortcutProbabilities.get(i)));
- }
}
// TODO: Remove
@@ -154,7 +137,6 @@ public final class WordProperty implements Comparable<WordProperty> {
return Arrays.hashCode(new Object[] {
word.mWord,
word.mProbabilityInfo,
- word.mShortcutTargets,
word.mNgrams,
word.mIsNotAWord,
word.mIsPossiblyOffensive
@@ -185,10 +167,10 @@ public final class WordProperty implements Comparable<WordProperty> {
if (o == this) return true;
if (!(o instanceof WordProperty)) return false;
WordProperty w = (WordProperty)o;
- return mProbabilityInfo.equals(w.mProbabilityInfo) && mWord.equals(w.mWord)
- && mShortcutTargets.equals(w.mShortcutTargets) && equals(mNgrams, w.mNgrams)
+ return mProbabilityInfo.equals(w.mProbabilityInfo)
+ && mWord.equals(w.mWord) && equals(mNgrams, w.mNgrams)
&& mIsNotAWord == w.mIsNotAWord && mIsPossiblyOffensive == w.mIsPossiblyOffensive
- && mHasNgrams == w.mHasNgrams && mHasShortcuts && w.mHasNgrams;
+ && mHasNgrams == w.mHasNgrams;
}
// TDOO: Have a utility method like java.util.Objects.equals.
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 1d75a3098..b6286b203 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -25,7 +25,6 @@ import com.android.inputmethod.latin.ExpandableBinaryDictionary;
import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.define.ProductionFlags;
-import com.android.inputmethod.latin.utils.DistracterFilter;
import java.io.File;
import java.util.Locale;
@@ -94,15 +93,14 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas
* @param word the word the user inputted
* @param isValid whether the word is valid or not
* @param timestamp the timestamp when the word has been inputted
- * @param distracterFilter the filter to check whether the word is a distracter
*/
public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
@Nonnull final NgramContext ngramContext, final String word, final boolean isValid,
- final int timestamp, @Nonnull final DistracterFilter distracterFilter) {
+ final int timestamp) {
if (word.length() > DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH) {
return;
}
- userHistoryDictionary.updateEntriesForWordWithCheckingDistracter(ngramContext, word,
- isValid, 1 /* count */, timestamp, distracterFilter);
+ userHistoryDictionary.updateEntriesForWord(ngramContext, word,
+ isValid, 1 /* count */, timestamp);
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java
index 975396d2d..d9858e61f 100644
--- a/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/PreferencesSettingsFragment.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.latin.settings;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
+import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
@@ -38,6 +39,10 @@ import com.android.inputmethod.latin.RichInputMethodManager;
* - Voice input key
*/
public final class PreferencesSettingsFragment extends SubScreenFragment {
+
+ private static final boolean VOICE_IME_ENABLED =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+
@Override
public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
@@ -72,11 +77,9 @@ public final class PreferencesSettingsFragment extends SubScreenFragment {
final Preference voiceInputKeyOption = findPreference(Settings.PREF_VOICE_INPUT_KEY);
if (voiceInputKeyOption != null) {
RichInputMethodManager.getInstance().refreshSubtypeCaches();
- final boolean isShortcutImeEnabled = RichInputMethodManager.getInstance()
- .isShortcutImeEnabled();
- voiceInputKeyOption.setEnabled(isShortcutImeEnabled);
- voiceInputKeyOption.setSummary(
- isShortcutImeEnabled ? null : getText(R.string.voice_input_disabled_summary));
+ voiceInputKeyOption.setEnabled(VOICE_IME_ENABLED);
+ voiceInputKeyOption.setSummary(VOICE_IME_ENABLED
+ ? null : getText(R.string.voice_input_disabled_summary));
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 9a1bb7784..6224071ea 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -21,6 +21,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.os.Build;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
@@ -136,7 +137,7 @@ public class SettingsValues {
DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res)
&& mInputAttributes.mShouldShowVoiceInputKey
- && RichInputMethodManager.getInstance().isShortcutImeEnabled();
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
final String autoCorrectionThresholdRawValue = prefs.getString(
Settings.PREF_AUTO_CORRECTION_THRESHOLD,
res.getString(R.string.auto_correction_threshold_mode_index_modest));
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 02151522d..95293bf2f 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -24,6 +24,7 @@ import android.text.InputType;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.textservice.SuggestionsInfo;
+import android.util.Log;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
@@ -52,6 +53,9 @@ import java.util.concurrent.Semaphore;
*/
public final class AndroidSpellCheckerService extends SpellCheckerService
implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480;
@@ -80,6 +84,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public static final String SINGLE_QUOTE = "\u0027";
public static final String APOSTROPHE = "\u2019";
+ private UserDictionaryLookup mUserDictionaryLookup;
public AndroidSpellCheckerService() {
super();
@@ -95,6 +100,24 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
+ // Create a UserDictionaryLookup. It needs to be close()d and set to null in onDestroy.
+ if (mUserDictionaryLookup == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Creating mUserDictionaryLookup in onCreate");
+ }
+ mUserDictionaryLookup = new UserDictionaryLookup(this);
+ } else if (DEBUG) {
+ Log.d(TAG, "mUserDictionaryLookup already created before onCreate");
+ }
+ }
+
+ @Override public void onDestroy() {
+ if (DEBUG) {
+ Log.d(TAG, "Closing and dereferencing mUserDictionaryLookup in onDestroy");
+ }
+ mUserDictionaryLookup.close();
+ mUserDictionaryLookup = null;
+ super.onDestroy();
}
public float getRecommendedThreshold() {
@@ -150,6 +173,16 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public boolean isValidWord(final Locale locale, final String word) {
mSemaphore.acquireUninterruptibly();
try {
+ if (mUserDictionaryLookup.isValidWord(word, locale)) {
+ if (DEBUG) {
+ Log.d(TAG, "mUserDictionaryLookup.isValidWord(" + word + ")=true");
+ }
+ return true;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "mUserDictionaryLookup.isValidWord(" + word + ")=false");
+ }
+ }
DictionaryFacilitator dictionaryFacilitatorForLocale =
mDictionaryFacilitatorCache.get(locale);
return dictionaryFacilitatorForLocale.isValidSpellingWord(word);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java b/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java
new file mode 100644
index 000000000..baff8f066
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2015 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.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.UserDictionary;
+import android.util.Log;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.common.LocaleUtils;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * UserDictionaryLookup provides the ability to lookup into the system-wide "Personal dictionary".
+ *
+ * Note, that the initial dictionary loading happens asynchronously so it is possible (hopefully
+ * rarely) that isValidWord is called before the initial load has started.
+ *
+ * The caller should explicitly call close() when the object is no longer needed, in order to
+ * release any resources and references to this object. A service should create this object in
+ * onCreate and close() it in onDestroy.
+ */
+public class UserDictionaryLookup implements Closeable {
+ private static final String TAG = UserDictionaryLookup.class.getSimpleName();
+
+ /**
+ * This guards the execution of any Log.d() logging, so that if false, they are not even
+ */
+ private static final boolean DEBUG = false;
+
+ /**
+ * To avoid loading too many dictionary entries in memory, we cap them at this number. If
+ * that number is exceeded, the lowest-frequency items will be dropped. Note, there is no
+ * explicit cap on the number of locales in every entry.
+ */
+ private static final int MAX_NUM_ENTRIES = 1000;
+
+ /**
+ * The delay (in milliseconds) to impose on reloads. Previously scheduled reloads will be
+ * cancelled if a new reload is scheduled before the delay expires. Thus, only the last
+ * reload in the series of frequent reloads will execute.
+ *
+ * Note, this value should be low enough to allow the "Add to dictionary" feature in the
+ * TextView correction (red underline) drop-down menu to work properly in the following case:
+ *
+ * 1. User types OOV (out-of-vocabulary) word.
+ * 2. The OOV is red-underlined.
+ * 3. User selects "Add to dictionary". The red underline disappears while the OOV is
+ * in a composing span.
+ * 4. The user taps space. The red underline should NOT reappear. If this value is very
+ * high and the user performs the space tap fast enough, the red underline may reappear.
+ */
+ @UsedForTesting
+ static final int RELOAD_DELAY_MS = 200;
+
+ private final ContentResolver mResolver;
+
+ /**
+ * Executor on which to perform the initial load and subsequent reloads (after a delay).
+ */
+ private final ScheduledExecutorService mLoadExecutor =
+ Executors.newSingleThreadScheduledExecutor();
+
+ /**
+ * Runnable that calls loadUserDictionary().
+ */
+ private class UserDictionaryLoader implements Runnable {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.d(TAG, "Executing (re)load");
+ }
+ loadUserDictionary();
+ }
+ }
+ private final UserDictionaryLoader mLoader = new UserDictionaryLoader();
+
+ /**
+ * Content observer for UserDictionary changes. It has the following properties:
+ * 1. It spawns off a UserDictionary reload in another thread, after some delay.
+ * 2. It cancels previously scheduled reloads, and only executes the latest.
+ * 3. It may be called multiple times quickly in succession (and is in fact called so
+ * when UserDictionary is edited through its settings UI, when sometimes multiple
+ * notifications are sent for the edited entry, but also for the entire UserDictionary).
+ */
+ private class UserDictionaryContentObserver extends ContentObserver {
+ public UserDictionaryContentObserver() {
+ super(null);
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ // Support pre-API16 platforms.
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (DEBUG) {
+ Log.d(TAG, "Received content observer onChange notification for URI: " + uri);
+ }
+ // Cancel (but don't interrupt) any pending reloads (except the initial load).
+ if (mReloadFuture != null && !mReloadFuture.isCancelled() &&
+ !mReloadFuture.isDone()) {
+ // Note, that if already cancelled or done, this will do nothing.
+ boolean isCancelled = mReloadFuture.cancel(false);
+ if (DEBUG) {
+ if (isCancelled) {
+ Log.d(TAG, "Successfully canceled previous reload request");
+ } else {
+ Log.d(TAG, "Unable to cancel previous reload request");
+ }
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Scheduling reload in " + RELOAD_DELAY_MS + " ms");
+ }
+
+ // Schedule a new reload after RELOAD_DELAY_MS.
+ mReloadFuture = mLoadExecutor.schedule(mLoader, RELOAD_DELAY_MS, TimeUnit.MILLISECONDS);
+ }
+ }
+ private final ContentObserver mObserver = new UserDictionaryContentObserver();
+
+ /**
+ * Indicates that a load is in progress, so no need for another.
+ */
+ private AtomicBoolean mIsLoading = new AtomicBoolean(false);
+
+ /**
+ * Indicates that this lookup object has been close()d.
+ */
+ private AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+ /**
+ * We store a map from a dictionary word to the set of locales it belongs
+ * in. We then iterate over the set of locales to find a match using
+ * LocaleUtils.
+ */
+ private volatile HashMap<String, ArrayList<Locale>> mDictWords;
+
+ /**
+ * The last-scheduled reload future. Saved in order to cancel a pending reload if a new one
+ * is coming.
+ */
+ private volatile ScheduledFuture<?> mReloadFuture;
+
+ /**
+ * @param context the context from which to obtain content resolver
+ */
+ public UserDictionaryLookup(Context context) {
+ if (DEBUG) {
+ Log.d(TAG, "UserDictionaryLookup constructor with context: " + context);
+ }
+
+ // Obtain a content resolver.
+ mResolver = context.getContentResolver();
+
+ // Schedule the initial load to run immediately. It's possible that the first call to
+ // isValidWord occurs before the dictionary has actually loaded, so it should not
+ // assume that the dictionary has been loaded.
+ mLoadExecutor.schedule(mLoader, 0, TimeUnit.MILLISECONDS);
+
+ // Register the observer to be notified on changes to the UserDictionary and all individual
+ // items.
+ //
+ // If the user is interacting with the UserDictionary settings UI, or with the
+ // "Add to dictionary" drop-down option, duplicate notifications will be sent for the same
+ // edit: if a new entry is added, there is a notification for the entry itself, and
+ // separately for the entire dictionary. However, when used programmatically,
+ // only notifications for the specific edits are sent. Thus, the observer is registered to
+ // receive every possible notification, and instead has throttling logic to avoid doing too
+ // many reloads.
+ mResolver.registerContentObserver(
+ UserDictionary.Words.CONTENT_URI, true /* notifyForDescendents */, mObserver);
+ }
+
+ /**
+ * To be called by the garbage collector in the off chance that the service did not clean up
+ * properly. Do not rely on this getting called, and make sure close() is called explicitly.
+ */
+ @Override
+ public void finalize() throws Throwable {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Finalize called, calling close()");
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Cleans up UserDictionaryLookup: shuts down any extra threads and unregisters the observer.
+ *
+ * It is safe, but not advised to call this multiple times, and isValidWord would continue to
+ * work, but no data will be reloaded any longer.
+ */
+ @Override
+ public void close() {
+ if (DEBUG) {
+ Log.d(TAG, "Close called (no pun intended), cleaning up executor and observer");
+ }
+ if (mIsClosed.compareAndSet(false, true)) {
+ // Shut down the load executor.
+ mLoadExecutor.shutdown();
+
+ // Unregister the content observer.
+ mResolver.unregisterContentObserver(mObserver);
+ }
+ }
+
+ /**
+ * Returns true if the initial load has been performed.
+ *
+ * @return true if the initial load is successful
+ */
+ @UsedForTesting
+ boolean isLoaded() {
+ return mDictWords != null;
+ }
+
+ /**
+ * Determines if the given word is a valid word in the given locale based on the UserDictionary.
+ * It tries hard to find a match: for example, casing is ignored and if the word is present in a
+ * more general locale (e.g. en or all locales), and isValidWord is asking for a more specific
+ * locale (e.g. en_US), it will be considered a match.
+ *
+ * @param word the word to match
+ * @param locale the locale in which to match the word
+ * @return true iff the word has been matched for this locale in the UserDictionary.
+ */
+ public boolean isValidWord(
+ final String word, final Locale locale) {
+ if (!isLoaded()) {
+ // This is a corner case in the event the initial load of UserDictionary has not
+ // been loaded. In that case, we assume the word is not a valid word in
+ // UserDictionary.
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord invoked, but initial load not complete");
+ }
+ return false;
+ }
+
+ // Atomically obtain the current copy of mDictWords;
+ final HashMap<String, ArrayList<Locale>> dictWords = mDictWords;
+
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord invoked for word [" + word +
+ "] in locale " + locale);
+ }
+ // Lowercase the word using the given locale. Note, that dictionary
+ // words are lowercased using their locale, and theoretically the
+ // lowercasing between two matching locales may differ. For simplicity
+ // we ignore that possibility.
+ final String lowercased = word.toLowerCase(locale);
+ final ArrayList<Locale> dictLocales = dictWords.get(lowercased);
+ if (null == dictLocales) {
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord=false, since there is no entry for " +
+ "lowercased word [" + lowercased + "]");
+ }
+ return false;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord found an entry for lowercased word [" + lowercased +
+ "]; examining locales");
+ }
+ // Iterate over the locales this word is in.
+ for (final Locale dictLocale : dictLocales) {
+ final int matchLevel = LocaleUtils.getMatchLevel(dictLocale.toString(),
+ locale.toString());
+ if (DEBUG) {
+ Log.d(TAG, "matchLevel for dictLocale=" + dictLocale + ", locale=" +
+ locale + " is " + matchLevel);
+ }
+ if (LocaleUtils.isMatch(matchLevel)) {
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord=true, since matchLevel " + matchLevel +
+ " is a match");
+ }
+ return true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "matchLevel " + matchLevel + " is not a match");
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "isValidWord=false, since none of the locales matched");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Loads the UserDictionary in the current thread.
+ *
+ * Only one reload can happen at a time. If already running, will exit quickly.
+ */
+ private void loadUserDictionary() {
+ // Bail out if already in the process of loading.
+ if (!mIsLoading.compareAndSet(false, true)) {
+ if (DEBUG) {
+ Log.d(TAG, "Already in the process of loading UserDictionary, skipping");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Loading UserDictionary");
+ }
+ HashMap<String, ArrayList<Locale>> dictWords =
+ new HashMap<String, ArrayList<Locale>>();
+ // Load the UserDictionary. Request that items be returned in the default sort order
+ // for UserDictionary, which is by frequency.
+ Cursor cursor = mResolver.query(UserDictionary.Words.CONTENT_URI,
+ null, null, null, UserDictionary.Words.DEFAULT_SORT_ORDER);
+ if (null == cursor || cursor.getCount() < 1) {
+ if (DEBUG) {
+ Log.d(TAG, "No entries found in UserDictionary");
+ }
+ } else {
+ // Iterate over the entries in the UserDictionary. Note, that iteration is in
+ // descending frequency by default.
+ while (dictWords.size() < MAX_NUM_ENTRIES && cursor.moveToNext()) {
+ // If there is no column for locale, skip this entry. An empty
+ // locale on the other hand will not be skipped.
+ final int dictLocaleIndex = cursor.getColumnIndex(
+ UserDictionary.Words.LOCALE);
+ if (dictLocaleIndex < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Encountered UserDictionary entry " +
+ "without LOCALE, skipping");
+ }
+ continue;
+ }
+ // If there is no column for word, skip this entry.
+ final int dictWordIndex = cursor.getColumnIndex(
+ UserDictionary.Words.WORD);
+ if (dictWordIndex < 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Encountered UserDictionary entry without " +
+ "WORD, skipping");
+ }
+ continue;
+ }
+ // If the word is null, skip this entry.
+ final String rawDictWord = cursor.getString(dictWordIndex);
+ if (null == rawDictWord) {
+ if (DEBUG) {
+ Log.d(TAG, "Encountered null word");
+ }
+ continue;
+ }
+ // If the locale is null, that's interpreted to mean all locales. Note, the special
+ // zz locale for an Alphabet (QWERTY) layout will not match any actual language.
+ String localeString = cursor.getString(dictLocaleIndex);
+ if (null == localeString) {
+ if (DEBUG) {
+ Log.d(TAG, "Encountered null locale for word [" +
+ rawDictWord + "], assuming all locales");
+ }
+ // For purposes of LocaleUtils, an empty locale matches
+ // everything.
+ localeString = "";
+ }
+ final Locale dictLocale = LocaleUtils.constructLocaleFromString(
+ localeString);
+ // Lowercase the word before storing it.
+ final String dictWord = rawDictWord.toLowerCase(dictLocale);
+ if (DEBUG) {
+ Log.d(TAG, "Incorporating UserDictionary word [" + dictWord +
+ "] for locale " + dictLocale);
+ }
+ // Check if there is an existing entry for this word.
+ ArrayList<Locale> dictLocales = dictWords.get(dictWord);
+ if (null == dictLocales) {
+ // If there is no entry for this word, create one.
+ if (DEBUG) {
+ Log.d(TAG, "Word [" + dictWord +
+ "] not seen for other locales, creating new entry");
+ }
+ dictLocales = new ArrayList<Locale>();
+ dictWords.put(dictWord, dictLocales);
+ }
+ // Append the locale to the list of locales this word is in.
+ dictLocales.add(dictLocale);
+ }
+ }
+
+ // Atomically replace the copy of mDictWords.
+ mDictWords = dictWords;
+
+ // Allow other calls to loadUserDictionary to execute now.
+ mIsLoading.set(false);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index 69029c51e..cb615f3af 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -26,7 +26,6 @@ import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
-import com.android.inputmethod.compat.UserDictionaryCompatUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.common.LocaleUtils;
@@ -47,10 +46,7 @@ import javax.annotation.Nullable;
public class UserDictionaryAddWordContents {
public static final String EXTRA_MODE = "mode";
public static final String EXTRA_WORD = "word";
- public static final String EXTRA_SHORTCUT = "shortcut";
public static final String EXTRA_LOCALE = "locale";
- public static final String EXTRA_ORIGINAL_WORD = "originalWord";
- public static final String EXTRA_ORIGINAL_SHORTCUT = "originalShortcut";
public static final int MODE_EDIT = 0;
public static final int MODE_INSERT = 1;
@@ -63,20 +59,12 @@ public class UserDictionaryAddWordContents {
private final int mMode; // Either MODE_EDIT or MODE_INSERT
private final EditText mWordEditText;
- private final EditText mShortcutEditText;
private String mLocale;
private final String mOldWord;
- private final String mOldShortcut;
private String mSavedWord;
- private String mSavedShortcut;
/* package */ UserDictionaryAddWordContents(final View view, final Bundle args) {
mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text);
- mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut);
- if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
- mShortcutEditText.setVisibility(View.GONE);
- view.findViewById(R.id.user_dictionary_add_shortcut_label).setVisibility(View.GONE);
- }
final String word = args.getString(EXTRA_WORD);
if (null != word) {
mWordEditText.setText(word);
@@ -84,17 +72,6 @@ public class UserDictionaryAddWordContents {
// it's too long to be edited.
mWordEditText.setSelection(mWordEditText.getText().length());
}
- final String shortcut;
- if (UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
- shortcut = args.getString(EXTRA_SHORTCUT);
- if (null != shortcut && null != mShortcutEditText) {
- mShortcutEditText.setText(shortcut);
- }
- mOldShortcut = args.getString(EXTRA_SHORTCUT);
- } else {
- shortcut = null;
- mOldShortcut = null;
- }
mMode = args.getInt(EXTRA_MODE); // default return value for #getInt() is 0 = MODE_EDIT
mOldWord = args.getString(EXTRA_WORD);
updateLocale(args.getString(EXTRA_LOCALE));
@@ -103,10 +80,8 @@ public class UserDictionaryAddWordContents {
/* package */ UserDictionaryAddWordContents(final View view,
final UserDictionaryAddWordContents oldInstanceToBeEdited) {
mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text);
- mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut);
mMode = MODE_EDIT;
mOldWord = oldInstanceToBeEdited.mSavedWord;
- mOldShortcut = oldInstanceToBeEdited.mSavedShortcut;
updateLocale(mLocale);
}
@@ -118,13 +93,6 @@ public class UserDictionaryAddWordContents {
/* package */ void saveStateIntoBundle(final Bundle outState) {
outState.putString(EXTRA_WORD, mWordEditText.getText().toString());
- outState.putString(EXTRA_ORIGINAL_WORD, mOldWord);
- if (null != mShortcutEditText) {
- outState.putString(EXTRA_SHORTCUT, mShortcutEditText.getText().toString());
- }
- if (null != mOldShortcut) {
- outState.putString(EXTRA_ORIGINAL_SHORTCUT, mOldShortcut);
- }
outState.putString(EXTRA_LOCALE, mLocale);
}
@@ -132,7 +100,7 @@ public class UserDictionaryAddWordContents {
if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
// Mode edit: remove the old entry.
final ContentResolver resolver = context.getContentResolver();
- UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
+ UserDictionarySettings.deleteWord(mOldWord, resolver);
}
// If we are in add mode, nothing was added, so we don't need to do anything.
}
@@ -143,50 +111,31 @@ public class UserDictionaryAddWordContents {
final ContentResolver resolver = context.getContentResolver();
if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) {
// Mode edit: remove the old entry.
- UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver);
+ UserDictionarySettings.deleteWord(mOldWord, resolver);
}
final String newWord = mWordEditText.getText().toString();
- final String newShortcut;
- if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
- newShortcut = null;
- } else if (null == mShortcutEditText) {
- newShortcut = null;
- } else {
- final String tmpShortcut = mShortcutEditText.getText().toString();
- if (TextUtils.isEmpty(tmpShortcut)) {
- newShortcut = null;
- } else {
- newShortcut = tmpShortcut;
- }
- }
if (TextUtils.isEmpty(newWord)) {
// If the word is somehow empty, don't insert it.
return CODE_CANCEL;
}
mSavedWord = newWord;
- mSavedShortcut = newShortcut;
- // If there is no shortcut, and the word already exists in the database, then we
- // should not insert, because either A. the word exists with no shortcut, in which
- // case the exact same thing we want to insert is already there, or B. the word
- // exists with at least one shortcut, in which case it has priority on our word.
- if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) {
+ // If the word already exists in the database, then we should not insert.
+ if (hasWord(newWord, context)) {
return CODE_ALREADY_PRESENT;
}
- // Disallow duplicates. If the same word with no shortcut is defined, remove it; if
- // the same word with the same shortcut is defined, remove it; but we don't mind if
- // there is the same word with a different, non-empty shortcut.
- UserDictionarySettings.deleteWord(newWord, null, resolver);
- if (!TextUtils.isEmpty(newShortcut)) {
- // If newShortcut is empty we just deleted this, no need to do it again
- UserDictionarySettings.deleteWord(newWord, newShortcut, resolver);
- }
+ // Disallow duplicates. If the same word is defined, remove it.
+ UserDictionarySettings.deleteWord(newWord, resolver);
// In this class we use the empty string to represent 'all locales' and mLocale cannot
// be null. However the addWord method takes null to mean 'all locales'.
- UserDictionaryCompatUtils.addWord(context, newWord.toString(),
- FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut, TextUtils.isEmpty(mLocale) ?
- null : LocaleUtils.constructLocaleFromString(mLocale));
+ final Locale locale = TextUtils.isEmpty(mLocale) ?
+ null : LocaleUtils.constructLocaleFromString(mLocale);
+ final Locale currentLocale = context.getResources().getConfiguration().locale;
+ final boolean useCurrentLocale = currentLocale.equals(locale);
+ UserDictionary.Words.addWord(context, newWord.toString(),
+ FREQUENCY_FOR_USER_DICTIONARY_ADDS, null /* shortcut */,
+ useCurrentLocale ? Locale.getDefault() : null);
return CODE_WORD_ADDED;
}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 6254b54ff..57347ce8c 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.os.Build;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
@@ -74,7 +75,7 @@ public class UserDictionaryList extends PreferenceFragment {
} finally {
cursor.close();
}
- if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
// For ICS, we need to show "For all languages" in case that the keyboard locale
// is different from the system locale
localeSet.add("");
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
index fabd49f46..3ccd1b3e6 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -48,42 +48,18 @@ import java.util.Locale;
public class UserDictionarySettings extends ListFragment {
- public static final boolean IS_SHORTCUT_API_SUPPORTED =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
-
- private static final String[] QUERY_PROJECTION_SHORTCUT_UNSUPPORTED =
- { UserDictionary.Words._ID, UserDictionary.Words.WORD};
- private static final String[] QUERY_PROJECTION_SHORTCUT_SUPPORTED =
- { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT};
- private static final String[] QUERY_PROJECTION =
- IS_SHORTCUT_API_SUPPORTED ?
- QUERY_PROJECTION_SHORTCUT_SUPPORTED : QUERY_PROJECTION_SHORTCUT_UNSUPPORTED;
-
- // The index of the shortcut in the above array.
- private static final int INDEX_SHORTCUT = 2;
-
- private static final String[] ADAPTER_FROM_SHORTCUT_UNSUPPORTED = {
- UserDictionary.Words.WORD,
+ private static final String[] QUERY_PROJECTION = {
+ UserDictionary.Words._ID, UserDictionary.Words.WORD
};
- private static final String[] ADAPTER_FROM_SHORTCUT_SUPPORTED = {
- UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT
+ private static final String[] ADAPTER_FROM = {
+ UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT
};
- private static final String[] ADAPTER_FROM = IS_SHORTCUT_API_SUPPORTED ?
- ADAPTER_FROM_SHORTCUT_SUPPORTED : ADAPTER_FROM_SHORTCUT_UNSUPPORTED;
-
- private static final int[] ADAPTER_TO_SHORTCUT_UNSUPPORTED = {
+ private static final int[] ADAPTER_TO = {
android.R.id.text1,
};
- private static final int[] ADAPTER_TO_SHORTCUT_SUPPORTED = {
- android.R.id.text1, android.R.id.text2
- };
-
- private static final int[] ADAPTER_TO = IS_SHORTCUT_API_SUPPORTED ?
- ADAPTER_TO_SHORTCUT_SUPPORTED : ADAPTER_TO_SHORTCUT_UNSUPPORTED;
-
// Either the locale is empty (means the word is applicable to all locales)
// or the word equals our current locale
private static final String QUERY_SELECTION =
@@ -91,13 +67,7 @@ public class UserDictionarySettings extends ListFragment {
private static final String QUERY_SELECTION_ALL_LOCALES =
UserDictionary.Words.LOCALE + " is null";
- private static final String DELETE_SELECTION_WITH_SHORTCUT = UserDictionary.Words.WORD
- + "=? AND " + UserDictionary.Words.SHORTCUT + "=?";
- private static final String DELETE_SELECTION_WITHOUT_SHORTCUT = UserDictionary.Words.WORD
- + "=? AND " + UserDictionary.Words.SHORTCUT + " is null OR "
- + UserDictionary.Words.SHORTCUT + "=''";
- private static final String DELETE_SELECTION_SHORTCUT_UNSUPPORTED =
- UserDictionary.Words.WORD + "=?";
+ private static final String DELETE_SELECTION = UserDictionary.Words.WORD + "=?";
private static final int OPTIONS_MENU_ADD = Menu.FIRST;
@@ -192,20 +162,18 @@ public class UserDictionarySettings extends ListFragment {
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
final String word = getWord(position);
- final String shortcut = getShortcut(position);
if (word != null) {
- showAddOrEditDialog(word, shortcut);
+ showAddOrEditDialog(word);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
final Locale systemLocale = getResources().getConfiguration().locale;
if (!TextUtils.isEmpty(mLocale) && !mLocale.equals(systemLocale.toString())) {
// Hide the add button for ICS because it doesn't support specifying a locale
- // for an entry. This new "locale"-aware API has been added in conjunction
- // with the shortcut API.
+ // for an entry.
return;
}
}
@@ -219,7 +187,7 @@ public class UserDictionarySettings extends ListFragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == OPTIONS_MENU_ADD) {
- showAddOrEditDialog(null, null);
+ showAddOrEditDialog(null);
return true;
}
return false;
@@ -228,18 +196,15 @@ public class UserDictionarySettings extends ListFragment {
/**
* Add or edit a word. If editingWord is null, it's an add; otherwise, it's an edit.
* @param editingWord the word to edit, or null if it's an add.
- * @param editingShortcut the shortcut for this entry, or null if none.
*/
- private void showAddOrEditDialog(final String editingWord, final String editingShortcut) {
+ private void showAddOrEditDialog(final String editingWord) {
final Bundle args = new Bundle();
args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, null == editingWord
? UserDictionaryAddWordContents.MODE_INSERT
: UserDictionaryAddWordContents.MODE_EDIT);
args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord);
- args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut);
args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale);
- android.preference.PreferenceActivity pa =
- (android.preference.PreferenceActivity)getActivity();
+ getActivity();
}
private String getWord(final int position) {
@@ -252,31 +217,8 @@ public class UserDictionarySettings extends ListFragment {
mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD));
}
- private String getShortcut(final int position) {
- if (!IS_SHORTCUT_API_SUPPORTED) return null;
- if (null == mCursor) return null;
- mCursor.moveToPosition(position);
- // Handle a possible race-condition
- if (mCursor.isAfterLast()) return null;
-
- return mCursor.getString(
- mCursor.getColumnIndexOrThrow(UserDictionary.Words.SHORTCUT));
- }
-
- public static void deleteWord(final String word, final String shortcut,
- final ContentResolver resolver) {
- if (!IS_SHORTCUT_API_SUPPORTED) {
- resolver.delete(UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_SHORTCUT_UNSUPPORTED,
- new String[] { word });
- } else if (TextUtils.isEmpty(shortcut)) {
- resolver.delete(
- UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT,
- new String[] { word });
- } else {
- resolver.delete(
- UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT,
- new String[] { word, shortcut });
- }
+ public static void deleteWord(final String word, final ContentResolver resolver) {
+ resolver.delete(UserDictionary.Words.CONTENT_URI, DELETE_SELECTION, new String[] { word });
}
private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer {
@@ -286,22 +228,7 @@ public class UserDictionarySettings extends ListFragment {
@Override
public boolean setViewValue(final View v, final Cursor c, final int columnIndex) {
- if (!IS_SHORTCUT_API_SUPPORTED) {
- // just let SimpleCursorAdapter set the view values
- return false;
- }
- if (columnIndex == INDEX_SHORTCUT) {
- final String shortcut = c.getString(INDEX_SHORTCUT);
- if (TextUtils.isEmpty(shortcut)) {
- v.setVisibility(View.GONE);
- } else {
- ((TextView)v).setText(shortcut);
- v.setVisibility(View.VISIBLE);
- }
- v.invalidate();
- return true;
- }
-
+ // just let SimpleCursorAdapter set the view values
return false;
}
};
diff --git a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
index 476c13406..5c0c4328f 100644
--- a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
@@ -19,7 +19,6 @@ package com.android.inputmethod.latin.utils;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.NgramProperty;
import com.android.inputmethod.latin.makedict.ProbabilityInfo;
-import com.android.inputmethod.latin.makedict.WeightedString;
import com.android.inputmethod.latin.makedict.WordProperty;
import java.util.HashMap;
@@ -29,7 +28,6 @@ public class CombinedFormatUtils {
public static final String BIGRAM_TAG = "bigram";
public static final String NGRAM_TAG = "ngram";
public static final String NGRAM_PREV_WORD_TAG = "prev_word";
- public static final String SHORTCUT_TAG = "shortcut";
public static final String PROBABILITY_TAG = "f";
public static final String HISTORICAL_INFO_TAG = "historicalInfo";
public static final String HISTORICAL_INFO_SEPARATOR = ":";
@@ -71,14 +69,6 @@ public class CombinedFormatUtils {
builder.append("," + POSSIBLY_OFFENSIVE_TAG + "=" + TRUE_VALUE);
}
builder.append("\n");
- if (wordProperty.mHasShortcuts) {
- for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
- builder.append(" " + SHORTCUT_TAG + "=" + shortcutTarget.mWord);
- builder.append(",");
- builder.append(formatProbabilityInfo(shortcutTarget.mProbabilityInfo));
- builder.append("\n");
- }
- }
if (wordProperty.mHasNgrams) {
for (final NgramProperty ngramProperty : wordProperty.mNgrams) {
builder.append(" " + NGRAM_TAG + "=" + ngramProperty.mTargetWord.mWord);
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index e355b7e1f..2e9cc8845 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -51,6 +51,7 @@ public class DictionaryInfoUtils {
private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
private static final String DEFAULT_MAIN_DICT = "main";
private static final String MAIN_DICT_PREFIX = "main_";
+ private static final String DECODER_DICT_SUFFIX = DecoderSpecificConstants.DECODER_DICT_SUFFIX;
// 6 digits - unicode is limited to 21 bits
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
@@ -267,8 +268,8 @@ public class DictionaryInfoUtils {
int resId;
// Try to find main_language_country dictionary.
if (!locale.getCountry().isEmpty()) {
- final String dictLanguageCountry =
- MAIN_DICT_PREFIX + locale.toString().toLowerCase(Locale.ROOT);
+ final String dictLanguageCountry = MAIN_DICT_PREFIX
+ + locale.toString().toLowerCase(Locale.ROOT) + DECODER_DICT_SUFFIX;
if ((resId = res.getIdentifier(
dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
return resId;
@@ -276,7 +277,7 @@ public class DictionaryInfoUtils {
}
// Try to find main_language dictionary.
- final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage();
+ final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage() + DECODER_DICT_SUFFIX;
if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
return resId;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
deleted file mode 100644
index 525212c96..000000000
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.utils;
-
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.NgramContext;
-
-import java.util.List;
-import java.util.Locale;
-
-import javax.annotation.Nonnull;
-
-public interface DistracterFilter {
- /**
- * Determine whether a word is a distracter to words in dictionaries.
- *
- * @param ngramContext the n-gram context
- * @param testedWord the word that will be tested to see whether it is a distracter to words
- * in dictionaries.
- * @param locale the locale of word.
- * @return true if testedWord is a distracter, otherwise false.
- */
- public boolean isDistracterToWordsInDictionaries(final NgramContext ngramContext,
- final String testedWord, final Locale locale);
-
- @UsedForTesting
- public int getWordHandlingType(final NgramContext ngramContext, final String testedWord,
- final Locale locale);
-
- public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
-
- public void close();
-
- public static final class HandlingType {
- private final static int REQUIRE_NO_SPECIAL_HANDLINGS = 0x0;
- private final static int SHOULD_BE_LOWER_CASED = 0x1;
- private final static int SHOULD_BE_HANDLED_AS_OOV = 0x2;
-
- public static int getHandlingType(final boolean shouldBeLowerCased, final boolean isOov) {
- int wordHandlingType = HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
- if (shouldBeLowerCased) {
- wordHandlingType |= HandlingType.SHOULD_BE_LOWER_CASED;
- }
- if (isOov) {
- wordHandlingType |= HandlingType.SHOULD_BE_HANDLED_AS_OOV;
- }
- return wordHandlingType;
- }
-
- public static boolean shouldBeLowerCased(final int handlingType) {
- return (handlingType & SHOULD_BE_LOWER_CASED) != 0;
- }
-
- public static boolean shouldBeHandledAsOov(final int handlingType) {
- return (handlingType & SHOULD_BE_HANDLED_AS_OOV) != 0;
- }
- }
-
- @Nonnull
- public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() {
- @Override
- public boolean isDistracterToWordsInDictionaries(NgramContext ngramContext,
- String testedWord, Locale locale) {
- return false;
- }
-
- @Override
- public int getWordHandlingType(final NgramContext ngramContext,
- final String testedWord, final Locale locale) {
- return HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
- }
-
- @Override
- public void close() {
- }
-
- @Override
- public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
- }
- };
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
deleted file mode 100644
index becf13fd9..000000000
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * 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.utils;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.InputType;
-import android.util.Log;
-import android.util.LruCache;
-import android.util.Pair;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
-import com.android.inputmethod.latin.NgramContext;
-import com.android.inputmethod.latin.RichInputMethodSubtype;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.common.StringUtils;
-import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-
-/**
- * This class is used to prevent distracters being added to personalization
- * or user history dictionaries
- */
-public class DistracterFilterCheckingExactMatchesAndSuggestions implements DistracterFilter {
- private static final String TAG =
- DistracterFilterCheckingExactMatchesAndSuggestions.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private static final int MAX_DISTRACTERS_CACHE_SIZE = 1024;
-
- private final Context mContext;
- private final ConcurrentHashMap<Locale, InputMethodSubtype> mLocaleToSubtypeCache;
- private final ConcurrentHashMap<Locale, Keyboard> mLocaleToKeyboardCache;
- private final DictionaryFacilitatorLruCache mDictionaryFacilitatorLruCache;
- // The key is a pair of a locale and a word. The value indicates the word is a distracter to
- // words of the locale.
- private final LruCache<Pair<Locale, String>, Boolean> mDistractersCache;
- private final Object mLock = new Object();
-
- // If the score of the top suggestion exceeds this value, the tested word (e.g.,
- // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to
- // words in dictionary. The greater the threshold is, the less likely the tested word would
- // become a distracter, which means the tested word will be more likely to be added to
- // the dictionary.
- private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 0.4f;
-
- /**
- * Create a DistracterFilter instance.
- *
- * @param context the context.
- */
- public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context) {
- mContext = context;
- mLocaleToSubtypeCache = new ConcurrentHashMap<>();
- mLocaleToKeyboardCache = new ConcurrentHashMap<>();
- mDictionaryFacilitatorLruCache = new DictionaryFacilitatorLruCache(
- context, "" /* dictionaryNamePrefix */);
- mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
- }
-
- @Override
- public void close() {
- mLocaleToSubtypeCache.clear();
- mLocaleToKeyboardCache.clear();
- mDictionaryFacilitatorLruCache.evictAll();
- // Don't clear mDistractersCache.
- }
-
- @Override
- public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
- final Map<Locale, InputMethodSubtype> newLocaleToSubtypeMap = new HashMap<>();
- if (enabledSubtypes != null) {
- for (final InputMethodSubtype subtype : enabledSubtypes) {
- final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
- if (newLocaleToSubtypeMap.containsKey(locale)) {
- // Multiple subtypes are enabled for one locale.
- // TODO: Investigate what we should do for this case.
- continue;
- }
- newLocaleToSubtypeMap.put(locale, subtype);
- }
- }
- if (mLocaleToSubtypeCache.equals(newLocaleToSubtypeMap)) {
- // Enabled subtypes have not been changed.
- return;
- }
- // Update subtype and keyboard map for locales that are in the current mapping.
- for (final Locale locale: mLocaleToSubtypeCache.keySet()) {
- if (newLocaleToSubtypeMap.containsKey(locale)) {
- final InputMethodSubtype newSubtype = newLocaleToSubtypeMap.remove(locale);
- if (newSubtype.equals(newLocaleToSubtypeMap.get(locale))) {
- // Mapping has not been changed.
- continue;
- }
- mLocaleToSubtypeCache.replace(locale, newSubtype);
- } else {
- mLocaleToSubtypeCache.remove(locale);
- }
- mLocaleToKeyboardCache.remove(locale);
- }
- // Add locales that are not in the current mapping.
- mLocaleToSubtypeCache.putAll(newLocaleToSubtypeMap);
- }
-
- private Keyboard getKeyboardForLocale(final Locale locale) {
- final Keyboard cachedKeyboard = mLocaleToKeyboardCache.get(locale);
- if (cachedKeyboard != null) {
- return cachedKeyboard;
- }
- final InputMethodSubtype subtype = mLocaleToSubtypeCache.get(locale);
- if (subtype == null) {
- return null;
- }
- final EditorInfo editorInfo = new EditorInfo();
- editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
- final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
- mContext, editorInfo);
- final Resources res = mContext.getResources();
- final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
- final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
- builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
- builder.setSubtype(new RichInputMethodSubtype(subtype));
- builder.setIsSpellChecker(false /* isSpellChecker */);
- final KeyboardLayoutSet layoutSet = builder.build();
- final Keyboard newKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
- mLocaleToKeyboardCache.put(locale, newKeyboard);
- return newKeyboard;
- }
-
- /**
- * Determine whether a word is a distracter to words in dictionaries.
- *
- * @param ngramContext the n-gram context. Not used for now.
- * @param testedWord the word that will be tested to see whether it is a distracter to words
- * in dictionaries.
- * @param locale the locale of word.
- * @return true if testedWord is a distracter, otherwise false.
- */
- @Override
- public boolean isDistracterToWordsInDictionaries(final NgramContext ngramContext,
- final String testedWord, final Locale locale) {
- if (locale == null) {
- return false;
- }
- if (!mLocaleToSubtypeCache.containsKey(locale)) {
- Log.e(TAG, "Locale " + locale + " is not enabled.");
- // TODO: Investigate what we should do for disabled locales.
- return false;
- }
- final DictionaryFacilitator dictionaryFacilitator =
- mDictionaryFacilitatorLruCache.get(locale);
- if (DEBUG) {
- Log.d(TAG, "testedWord: " + testedWord);
- }
- final Pair<Locale, String> cacheKey = new Pair<>(locale, testedWord);
- final Boolean isCachedDistracter = mDistractersCache.get(cacheKey);
- if (isCachedDistracter != null && isCachedDistracter) {
- if (DEBUG) {
- Log.d(TAG, "isDistracter: true (cache hit)");
- }
- return true;
- }
-
- final boolean isDistracterCheckedByGetMaxFreqencyOfExactMatches =
- checkDistracterUsingMaxFreqencyOfExactMatches(dictionaryFacilitator, testedWord);
- if (isDistracterCheckedByGetMaxFreqencyOfExactMatches) {
- // Add the pair of locale and word to the cache.
- mDistractersCache.put(cacheKey, Boolean.TRUE);
- return true;
- }
- if (dictionaryFacilitator.isValidSuggestionWord(testedWord)) {
- // Valid word is not a distracter.
- if (DEBUG) {
- Log.d(TAG, "isDistracter: false (valid word)");
- }
- return false;
- }
-
- final Keyboard keyboard = getKeyboardForLocale(locale);
- final boolean isDistracterCheckedByGetSuggestion =
- checkDistracterUsingGetSuggestions(dictionaryFacilitator, keyboard, testedWord);
- if (isDistracterCheckedByGetSuggestion) {
- // Add the pair of locale and word to the cache.
- mDistractersCache.put(cacheKey, Boolean.TRUE);
- return true;
- }
- return false;
- }
-
- private static boolean checkDistracterUsingMaxFreqencyOfExactMatches(
- final DictionaryFacilitator dictionaryFacilitator, final String testedWord) {
- // The tested word is a distracter when there is a word that is exact matched to the tested
- // word and its probability is higher than the tested word's probability.
- final int perfectMatchFreq = dictionaryFacilitator.getFrequency(testedWord);
- final int exactMatchFreq = dictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
- final boolean isDistracter = perfectMatchFreq < exactMatchFreq;
- if (DEBUG) {
- Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq);
- Log.d(TAG, "exactMatchFreq: " + exactMatchFreq);
- Log.d(TAG, "isDistracter: " + isDistracter);
- }
- return isDistracter;
- }
-
- private boolean checkDistracterUsingGetSuggestions(
- final DictionaryFacilitator dictionaryFacilitator, final Keyboard keyboard,
- final String testedWord) {
- if (keyboard == null) {
- return false;
- }
- final SettingsValuesForSuggestion settingsValuesForSuggestion =
- new SettingsValuesForSuggestion(false /* blockPotentiallyOffensive */,
- false /* spaceAwareGestureEnabled */);
- final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord);
- final String consideredWord = trailingSingleQuotesCount > 0 ?
- testedWord.substring(0, testedWord.length() - trailingSingleQuotesCount) :
- testedWord;
- final WordComposer composer = new WordComposer();
- final int[] codePoints = StringUtils.toCodePointArray(testedWord);
- final int[] coordinates = keyboard.getCoordinates(codePoints);
- composer.setComposingWord(codePoints, coordinates);
- final SuggestionResults suggestionResults;
- synchronized (mLock) {
- suggestionResults = dictionaryFacilitator.getSuggestionResults(composer,
- NgramContext.EMPTY_PREV_WORDS_INFO,
- keyboard.getProximityInfo().getNativeProximityInfo(),
- settingsValuesForSuggestion, 0 /* sessionId */,
- SuggestedWords.INPUT_STYLE_TYPING,
- keyboard.getKeyboardLayout());
- }
- if (suggestionResults.isEmpty()) {
- return false;
- }
- final SuggestedWordInfo firstSuggestion = suggestionResults.first();
- final boolean isDistracter = suggestionExceedsDistracterThreshold(
- firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
- if (DEBUG) {
- Log.d(TAG, "isDistracter: " + isDistracter);
- }
- return isDistracter;
- }
-
- private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion,
- final String consideredWord, final float distracterThreshold) {
- if (suggestion == null) {
- return false;
- }
- final int suggestionScore = suggestion.mScore;
- final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
- consideredWord, suggestion.mWord, suggestionScore);
- if (DEBUG) {
- Log.d(TAG, "normalizedScore: " + normalizedScore);
- Log.d(TAG, "distracterThreshold: " + distracterThreshold);
- }
- if (normalizedScore > distracterThreshold) {
- return true;
- }
- return false;
- }
-
- private boolean shouldBeLowerCased(final NgramContext ngramContext, final String testedWord,
- final Locale locale) {
- final DictionaryFacilitator dictionaryFacilitator =
- mDictionaryFacilitatorLruCache.get(locale);
- if (dictionaryFacilitator.isValidSuggestionWord(testedWord)) {
- return false;
- }
- final String lowerCaseWord = testedWord.toLowerCase(locale);
- if (testedWord.equals(lowerCaseWord)) {
- return false;
- }
- if (dictionaryFacilitator.isValidSuggestionWord(lowerCaseWord)) {
- return true;
- }
- if (StringUtils.getCapitalizationType(testedWord) == StringUtils.CAPITALIZE_FIRST
- && !ngramContext.isValid()) {
- // TODO: Check beginning-of-sentence.
- return true;
- }
- return false;
- }
-
- @Override
- public int getWordHandlingType(final NgramContext ngramContext, final String testedWord,
- final Locale locale) {
- // TODO: Use this method for user history dictionary.
- if (testedWord == null|| locale == null) {
- return HandlingType.getHandlingType(false /* shouldBeLowerCased */, false /* isOov */);
- }
- final boolean shouldBeLowerCased = shouldBeLowerCased(ngramContext, testedWord, locale);
- final String caseModifiedWord = shouldBeLowerCased
- ? testedWord.toLowerCase(locale) : testedWord;
- final boolean isOov = !mDictionaryFacilitatorLruCache.get(locale).isValidSuggestionWord(
- caseModifiedWord);
- return HandlingType.getHandlingType(shouldBeLowerCased, isOov);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
deleted file mode 100644
index 4c99fed9f..000000000
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.utils;
-
-import java.util.List;
-import java.util.Locale;
-
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.NgramContext;
-
-public class DistracterFilterCheckingIsInDictionary implements DistracterFilter {
- private final DistracterFilter mDistracterFilter;
- private final Dictionary mDictionary;
-
- public DistracterFilterCheckingIsInDictionary(final DistracterFilter distracterFilter,
- final Dictionary dictionary) {
- mDistracterFilter = distracterFilter;
- mDictionary = dictionary;
- }
-
- @Override
- public boolean isDistracterToWordsInDictionaries(NgramContext ngramContext,
- String testedWord, Locale locale) {
- if (mDictionary.isInDictionary(testedWord)) {
- // This filter treats entries that are already in the dictionary as non-distracters
- // because they have passed the filtering in the past.
- return false;
- }
- return mDistracterFilter.isDistracterToWordsInDictionaries(
- ngramContext, testedWord, locale);
- }
-
- @Override
- public int getWordHandlingType(final NgramContext ngramContext, final String testedWord,
- final Locale locale) {
- return mDistracterFilter.getWordHandlingType(ngramContext, testedWord, locale);
- }
-
- @Override
- public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
- // Do nothing.
- }
-
- @Override
- public void close() {
- // Do nothing.
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
index e77f6fd40..50be16072 100644
--- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -21,13 +21,14 @@ import com.android.inputmethod.annotations.UsedForTesting;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
/**
* Utilities to manage executors.
*/
public class ExecutorUtils {
- static final ConcurrentHashMap<String, ExecutorService> sExecutorMap =
+ static final ConcurrentHashMap<String, ScheduledExecutorService> sExecutorMap =
new ConcurrentHashMap<>();
private static class ThreadFactoryWithId implements ThreadFactory {
@@ -46,13 +47,14 @@ public class ExecutorUtils {
/**
* Gets the executor for the given id.
*/
- public static ExecutorService getExecutor(final String id) {
- ExecutorService executor = sExecutorMap.get(id);
+ public static ScheduledExecutorService getExecutor(final String id) {
+ ScheduledExecutorService executor = sExecutorMap.get(id);
if (executor == null) {
synchronized (sExecutorMap) {
executor = sExecutorMap.get(id);
if (executor == null) {
- executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id));
+ executor = Executors.newSingleThreadScheduledExecutor(
+ new ThreadFactoryWithId(id));
sExecutorMap.put(id, executor);
}
}
@@ -66,7 +68,7 @@ public class ExecutorUtils {
@UsedForTesting
public static void shutdownAllExecutors() {
synchronized (sExecutorMap) {
- for (final ExecutorService executor : sExecutorMap.values()) {
+ for (final ScheduledExecutorService executor : sExecutorMap.values()) {
executor.execute(new Runnable() {
@Override
public void run() {
diff --git a/java/src/com/android/inputmethod/latin/utils/NetworkConnectivityUtils.java b/java/src/com/android/inputmethod/latin/utils/NetworkConnectivityUtils.java
deleted file mode 100644
index 101c55067..000000000
--- a/java/src/com/android/inputmethod/latin/utils/NetworkConnectivityUtils.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.utils;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-
-import javax.annotation.Nonnull;
-
-/**
- * This class keeps track of the network connectivity state by receiving the system intent
- * {@link ConnectivityManager#CONNECTIVITY_ACTION}, and invokes an registered call back to notify
- * changes of the network connectivity state.
- */
-public final class NetworkConnectivityUtils {
- private static NetworkConnectivityReceiver sNetworkConnectivityReceiver;
-
- public interface NetworkStateChangeListener {
- /**
- * Called when the network connectivity state has changed.
- */
- public void onNetworkStateChanged();
- }
-
- private static class NetworkConnectivityReceiver extends BroadcastReceiver {
- @Nonnull
- private final NetworkStateChangeListener mListener;
- private boolean mIsNetworkConnected;
-
- public NetworkConnectivityReceiver(@Nonnull final NetworkStateChangeListener listener,
- final boolean isNetworkConnected) {
- mListener = listener;
- mIsNetworkConnected = isNetworkConnected;
- }
-
- public synchronized boolean isNetworkConnected() {
- return mIsNetworkConnected;
- }
-
- @Override
- public void onReceive(final Context context, final Intent intent) {
- final String action = intent.getAction();
- if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
- final boolean noConnection = intent.getBooleanExtra(
- ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
- synchronized (this) {
- mIsNetworkConnected = !noConnection;
- }
- mListener.onNetworkStateChanged();
- }
- }
- }
-
- private NetworkConnectivityUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static void onCreate(@Nonnull final Context context,
- @Nonnull final NetworkStateChangeListener listener) {
- final ConnectivityManager connectivityManager =
- (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- final NetworkInfo info = connectivityManager.getActiveNetworkInfo();
- final boolean isNetworkConnected = (info != null && info.isConnected());
-
- // Register {@link BroadcastReceiver} for the network connectivity state change.
- final NetworkConnectivityReceiver receiver = new NetworkConnectivityReceiver(
- listener, isNetworkConnected);
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- context.registerReceiver(receiver, filter);
-
- sNetworkConnectivityReceiver = receiver;
- }
-
- public static void onDestroy(final Context context) {
- context.unregisterReceiver(sNetworkConnectivityReceiver);
- }
-
- public static boolean isNetworkConnected() {
- final NetworkConnectivityReceiver receiver = sNetworkConnectivityReceiver;
- return receiver != null && receiver.isNetworkConnected();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
index 0e244666d..4679f7814 100644
--- a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
@@ -23,9 +23,10 @@ import java.util.TreeMap;
* A class to help with handling different writing scripts.
*/
public class ScriptUtils {
+
// Used for hardware keyboards
public static final int SCRIPT_UNKNOWN = -1;
- // TODO: should we use ISO 15924 identifiers instead?
+
public static final int SCRIPT_ARABIC = 0;
public static final int SCRIPT_ARMENIAN = 1;
public static final int SCRIPT_BENGALI = 2;
@@ -44,35 +45,31 @@ public class ScriptUtils {
public static final int SCRIPT_TAMIL = 15;
public static final int SCRIPT_TELUGU = 16;
public static final int SCRIPT_THAI = 17;
- public static final TreeMap<String, Integer> mSpellCheckerLanguageToScript;
+
+ private static final TreeMap<String, Integer> mLanguageCodeToScriptCode;
+
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.
- mSpellCheckerLanguageToScript = new TreeMap<>();
- mSpellCheckerLanguageToScript.put("cs", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("da", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("de", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("el", SCRIPT_GREEK);
- mSpellCheckerLanguageToScript.put("en", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("es", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("fi", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("fr", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("hr", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("it", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("lt", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("lv", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("nb", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("nl", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("pt", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("sl", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("ru", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode = new TreeMap<>();
+ mLanguageCodeToScriptCode.put("", SCRIPT_LATIN); // default
+ mLanguageCodeToScriptCode.put("ar", SCRIPT_ARABIC);
+ mLanguageCodeToScriptCode.put("hy", SCRIPT_ARMENIAN);
+ mLanguageCodeToScriptCode.put("bn", SCRIPT_BENGALI);
+ mLanguageCodeToScriptCode.put("bg", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode.put("sr", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode.put("ru", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode.put("ka", SCRIPT_GEORGIAN);
+ mLanguageCodeToScriptCode.put("el", SCRIPT_GREEK);
+ mLanguageCodeToScriptCode.put("he", SCRIPT_HEBREW);
+ mLanguageCodeToScriptCode.put("km", SCRIPT_KHMER);
+ mLanguageCodeToScriptCode.put("lo", SCRIPT_LAO);
+ mLanguageCodeToScriptCode.put("ml", SCRIPT_MALAYALAM);
+ mLanguageCodeToScriptCode.put("my", SCRIPT_MYANMAR);
+ mLanguageCodeToScriptCode.put("si", SCRIPT_SINHALA);
+ mLanguageCodeToScriptCode.put("ta", SCRIPT_TAMIL);
+ mLanguageCodeToScriptCode.put("te", SCRIPT_TELUGU);
+ mLanguageCodeToScriptCode.put("th", SCRIPT_THAI);
}
+
/*
* Returns whether the code point is a letter that makes sense for the specified
* locale for this spell checker.
@@ -181,11 +178,17 @@ public class ScriptUtils {
}
}
+ /**
+ * @param locale spell checker locale
+ * @return internal Latin IME script code that maps to a language code
+ * {@see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes}
+ */
public static int getScriptFromSpellCheckerLocale(final Locale locale) {
- final Integer script = mSpellCheckerLanguageToScript.get(locale.getLanguage());
- if (null == script) {
- throw new RuntimeException("We have been called with an unsupported language: \""
- + locale.getLanguage() + "\". Framework bug?");
+ String language = locale.getLanguage();
+ Integer script = mLanguageCodeToScriptCode.get(language);
+ if (script == null) {
+ // Default to Latin.
+ script = mLanguageCodeToScriptCode.get("");
}
return script;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
index e9a0e7a61..fc0a9cb6c 100644
--- a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
+++ b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
@@ -23,7 +23,6 @@ import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
-import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType;
import java.util.ArrayList;
import java.util.List;
@@ -41,17 +40,15 @@ public final class WordInputEventForPersonalization {
new int[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
public final boolean[] mIsPrevWordBeginningOfSentenceArray =
new boolean[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
- public final boolean mIsValid;
// Time stamp in seconds.
public final int mTimestamp;
@UsedForTesting
public WordInputEventForPersonalization(final CharSequence targetWord,
- final NgramContext ngramContext, final boolean isValid, final int timestamp) {
+ final NgramContext ngramContext, final int timestamp) {
mTargetWord = StringUtils.toCodePointArray(targetWord);
mPrevWordsCount = ngramContext.getPrevWordCount();
ngramContext.outputToArray(mPrevWordArray, mIsPrevWordBeginningOfSentenceArray);
- mIsValid = isValid;
mTimestamp = timestamp;
}
@@ -59,8 +56,7 @@ public final class WordInputEventForPersonalization {
// objects.
public static ArrayList<WordInputEventForPersonalization> createInputEventFrom(
final List<String> tokens, final int timestamp,
- final SpacingAndPunctuations spacingAndPunctuations, final Locale locale,
- final DistracterFilter distracterFilter) {
+ final SpacingAndPunctuations spacingAndPunctuations, final Locale locale) {
final ArrayList<WordInputEventForPersonalization> inputEvents = new ArrayList<>();
final int N = tokens.size();
NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
@@ -89,7 +85,7 @@ public final class WordInputEventForPersonalization {
}
final WordInputEventForPersonalization inputEvent =
detectWhetherVaildWordOrNotAndGetInputEvent(
- ngramContext, tempWord, timestamp, locale, distracterFilter);
+ ngramContext, tempWord, timestamp, locale);
if (inputEvent == null) {
continue;
}
@@ -101,19 +97,10 @@ public final class WordInputEventForPersonalization {
private static WordInputEventForPersonalization detectWhetherVaildWordOrNotAndGetInputEvent(
final NgramContext ngramContext, final String targetWord, final int timestamp,
- final Locale locale, final DistracterFilter distracterFilter) {
+ final Locale locale) {
if (locale == null) {
return null;
}
- final int wordHandlingType = distracterFilter.getWordHandlingType(ngramContext,
- targetWord, locale);
- final String word = HandlingType.shouldBeLowerCased(wordHandlingType) ?
- targetWord.toLowerCase(locale) : targetWord;
- if (distracterFilter.isDistracterToWordsInDictionaries(ngramContext, targetWord, locale)) {
- // The word is a distracter.
- return null;
- }
- return new WordInputEventForPersonalization(word, ngramContext,
- !HandlingType.shouldBeHandledAsOov(wordHandlingType), timestamp);
+ return new WordInputEventForPersonalization(targetWord, ngramContext, timestamp);
}
}