aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/src/com/android/inputmethod/latin/common/CollectionUtils.java13
-rw-r--r--java/res/values/strings.xml3
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java3
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java14
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java3
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java4
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java6
-rw-r--r--java/src/com/android/inputmethod/latin/UserDictionaryLookup.java (renamed from java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java)210
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java97
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java35
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java7
-rw-r--r--tests/src/com/android/inputmethod/latin/UserDictionaryLookupTest.java (renamed from tests/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookupTest.java)119
-rw-r--r--tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java8
-rw-r--r--tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java15
16 files changed, 400 insertions, 143 deletions
diff --git a/common/src/com/android/inputmethod/latin/common/CollectionUtils.java b/common/src/com/android/inputmethod/latin/common/CollectionUtils.java
index 48df413fd..80fae5f51 100644
--- a/common/src/com/android/inputmethod/latin/common/CollectionUtils.java
+++ b/common/src/com/android/inputmethod/latin/common/CollectionUtils.java
@@ -20,6 +20,7 @@ import com.android.inputmethod.annotations.UsedForTesting;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -60,7 +61,17 @@ public final class CollectionUtils {
* @return Whether c contains no elements.
*/
@UsedForTesting
- public static boolean isNullOrEmpty(@Nullable final Collection<?> c) {
+ public static boolean isNullOrEmpty(@Nullable final Collection c) {
return c == null || c.isEmpty();
}
+
+ /**
+ * Tests whether map contains no elements, true if map is null or map is empty.
+ * @param map Map to test.
+ * @return Whether map contains no elements.
+ */
+ @UsedForTesting
+ public static boolean isNullOrEmpty(@Nullable final Map map) {
+ return map == null || map.isEmpty();
+ }
}
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 4973a99f5..28dabf682 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -129,6 +129,9 @@
<!-- Description for option to enable auto capitalization of sentences -->
<string name="auto_cap_summary">Capitalize the first word of each sentence</string>
+ <!-- Option to edit personal dictionary. [CHAR_LIMIT=30]-->
+ <string name="edit_personal_dictionary">Personal dictionary</string>
+
<!-- Option to configure dictionaries -->
<string name="configure_dictionaries_title">Add-on dictionaries</string>
<!-- Name of the main dictionary, as opposed to auxiliary dictionaries (medical/entertainment/sports...) -->
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index a2789cc1a..fbc899192 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -34,6 +34,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
+import javax.annotation.Nullable;
+
/**
* Various helper functions for the state database
*/
@@ -705,6 +707,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
* @param version the word list version.
* @return the metadata about this word list.
*/
+ @Nullable
public static ContentValues getContentValuesByWordListId(final SQLiteDatabase db,
final String id, final int version) {
final Cursor cursor = db.query(METADATA_TABLE_NAME,
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
index 329b9f62e..e5d632fbe 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.dictionarypack;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.util.Log;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -30,10 +31,13 @@ import java.util.List;
* Helper class to easy up manipulation of dictionary pack metadata.
*/
public class MetadataHandler {
+
+ public static final String TAG = MetadataHandler.class.getSimpleName();
+
// The canonical file name for metadata. This is not the name of a real file on the
// device, but a symbolic name used in the database and in metadata handling. It is never
// tested against, only used for human-readability as the file name for the metadata.
- public final static String METADATA_FILENAME = "metadata.json";
+ public static final String METADATA_FILENAME = "metadata.json";
/**
* Reads the data from the cursor and store it in metadata objects.
@@ -114,6 +118,14 @@ public class MetadataHandler {
final String clientId, final String wordListId, final int version) {
final ContentValues contentValues = MetadataDbHelper.getContentValuesByWordListId(
MetadataDbHelper.getDb(context, clientId), wordListId, version);
+ if (contentValues == null) {
+ // TODO: Figure out why this would happen.
+ // Check if this happens when the metadata gets updated in the background.
+ Log.e(TAG, String.format( "Unable to find the current metadata for wordlist "
+ + "(clientId=%s, wordListId=%s, version=%d) on the database",
+ clientId, wordListId, version));
+ return null;
+ }
return WordListMetadata.createFromContentValues(contentValues);
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 30ff0b8ee..e720f3cd0 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -1143,6 +1143,9 @@ public final class UpdateHandler {
}
final WordListMetadata wordListMetaData = MetadataHandler.getCurrentMetadataForWordList(
context, clientId, wordlistId, version);
+ if (wordListMetaData == null) {
+ return;
+ }
final ActionBatch actions = new ActionBatch();
actions.add(new ActionBatch.StartDownloadAction(
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java b/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
index 59f75e4ed..99cffb816 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListMetadata.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.dictionarypack;
import android.content.ContentValues;
+import javax.annotation.Nonnull;
+
/**
* The metadata for a single word list.
*
@@ -77,7 +79,7 @@ public class WordListMetadata {
*
* If this lacks any required field, IllegalArgumentException is thrown.
*/
- public static WordListMetadata createFromContentValues(final ContentValues values) {
+ public static WordListMetadata createFromContentValues(@Nonnull final ContentValues values) {
final String id = values.getAsString(MetadataDbHelper.WORDLISTID_COLUMN);
final Integer type = values.getAsInteger(MetadataDbHelper.TYPE_COLUMN);
final String description = values.getAsString(MetadataDbHelper.DESCRIPTION_COLUMN);
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 16dcb3208..e00219c98 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -39,6 +39,10 @@ public abstract class Dictionary {
public static final String TYPE_USER_TYPED = "user_typed";
public static final PhonyDictionary DICTIONARY_USER_TYPED = new PhonyDictionary(TYPE_USER_TYPED);
+ public static final String TYPE_USER_SHORTCUT = "user_shortcut";
+ public static final PhonyDictionary DICTIONARY_USER_SHORTCUT =
+ new PhonyDictionary(TYPE_USER_SHORTCUT);
+
public static final String TYPE_APPLICATION_DEFINED = "application_defined";
public static final PhonyDictionary DICTIONARY_APPLICATION_DEFINED =
new PhonyDictionary(TYPE_APPLICATION_DEFINED);
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 37899d21e..907095746 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -650,7 +650,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
reloadDictionaryIfRequired();
final String dictName = mDictName;
final File dictFile = mDictFile;
- final AsyncResultHolder<DictionaryStats> result = new AsyncResultHolder<>();
+ final AsyncResultHolder<DictionaryStats> result =
+ new AsyncResultHolder<>("DictionaryStats");
asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
@Override
public void run() {
@@ -724,7 +725,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
public WordProperty[] getWordPropertiesForSyncing() {
reloadDictionaryIfRequired();
- final AsyncResultHolder<WordProperty[]> result = new AsyncResultHolder<>();
+ final AsyncResultHolder<WordProperty[]> result =
+ new AsyncResultHolder<>("WordPropertiesForSync");
asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
@Override
public void run() {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java b/java/src/com/android/inputmethod/latin/UserDictionaryLookup.java
index f2491f478..2569723b0 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionaryLookup.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin.spellcheck;
+package com.android.inputmethod.latin;
import android.content.ContentResolver;
import android.content.Context;
@@ -22,9 +22,11 @@ import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.UserDictionary;
+import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.common.CollectionUtils;
import com.android.inputmethod.latin.common.LocaleUtils;
import com.android.inputmethod.latin.utils.ExecutorUtils;
@@ -36,6 +38,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* UserDictionaryLookup provides the ability to lookup into the system-wide "Personal dictionary".
*
@@ -47,7 +52,6 @@ import java.util.concurrent.TimeUnit;
* 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
@@ -79,7 +83,12 @@ public class UserDictionaryLookup implements Closeable {
@UsedForTesting
static final int RELOAD_DELAY_MS = 200;
+ @UsedForTesting
+ static final Locale ANY_LOCALE = new Locale("");
+
+ private final String mTag;
private final ContentResolver mResolver;
+ private final String mServiceName;
/**
* Runnable that calls loadUserDictionary().
@@ -88,12 +97,11 @@ public class UserDictionaryLookup implements Closeable {
@Override
public void run() {
if (DEBUG) {
- Log.d(TAG, "Executing (re)load");
+ Log.d(mTag, "Executing (re)load");
}
loadUserDictionary();
}
}
- private final UserDictionaryLoader mLoader = new UserDictionaryLoader();
/**
* Content observer for UserDictionary changes. It has the following properties:
@@ -122,7 +130,7 @@ public class UserDictionaryLookup implements Closeable {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (DEBUG) {
- Log.d(TAG, "Received content observer onChange notification for URI: " + uri);
+ Log.d(mTag, "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() &&
@@ -131,20 +139,20 @@ public class UserDictionaryLookup implements Closeable {
boolean isCancelled = mReloadFuture.cancel(false);
if (DEBUG) {
if (isCancelled) {
- Log.d(TAG, "Successfully canceled previous reload request");
+ Log.d(mTag, "Successfully canceled previous reload request");
} else {
- Log.d(TAG, "Unable to cancel previous reload request");
+ Log.d(mTag, "Unable to cancel previous reload request");
}
}
}
if (DEBUG) {
- Log.d(TAG, "Scheduling reload in " + RELOAD_DELAY_MS + " ms");
+ Log.d(mTag, "Scheduling reload in " + RELOAD_DELAY_MS + " ms");
}
// Schedule a new reload after RELOAD_DELAY_MS.
- mReloadFuture = ExecutorUtils.getBackgroundExecutor(ExecutorUtils.SPELLING)
- .schedule(mLoader, RELOAD_DELAY_MS, TimeUnit.MILLISECONDS);
+ mReloadFuture = ExecutorUtils.getBackgroundExecutor(mServiceName)
+ .schedule(new UserDictionaryLoader(), RELOAD_DELAY_MS, TimeUnit.MILLISECONDS);
}
}
private final ContentObserver mObserver = new UserDictionaryContentObserver();
@@ -167,6 +175,12 @@ public class UserDictionaryLookup implements Closeable {
private volatile HashMap<String, ArrayList<Locale>> mDictWords;
/**
+ * We store a map from a shortcut to a word for each locale.
+ * Shortcuts that apply to any locale are keyed by {@link #ANY_LOCALE}.
+ */
+ private volatile HashMap<Locale, HashMap<String, String>> mShortcutsPerLocale;
+
+ /**
* The last-scheduled reload future. Saved in order to cancel a pending reload if a new one
* is coming.
*/
@@ -175,18 +189,24 @@ public class UserDictionaryLookup implements Closeable {
/**
* @param context the context from which to obtain content resolver
*/
- public UserDictionaryLookup(Context context) {
- if (DEBUG) {
- Log.d(TAG, "UserDictionaryLookup constructor with context: " + context);
- }
+ public UserDictionaryLookup(@Nonnull final Context context, @Nonnull final String serviceName) {
+ mTag = serviceName + ".UserDictionaryLookup";
+
+ Log.i(mTag, "create()");
+
+ mServiceName = serviceName;
// Obtain a content resolver.
mResolver = context.getContentResolver();
+ }
+
+ public void open() {
+ Log.i(mTag, "open()");
// 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.
- ExecutorUtils.getBackgroundExecutor(ExecutorUtils.SPELLING).execute(mLoader);
+ loadUserDictionary();
// Register the observer to be notified on changes to the UserDictionary and all individual
// items.
@@ -210,7 +230,7 @@ public class UserDictionaryLookup implements Closeable {
public void finalize() throws Throwable {
try {
if (DEBUG) {
- Log.d(TAG, "Finalize called, calling close()");
+ Log.d(mTag, "Finalize called, calling close()");
}
close();
} finally {
@@ -227,7 +247,7 @@ public class UserDictionaryLookup implements Closeable {
@Override
public void close() {
if (DEBUG) {
- Log.d(TAG, "Close called (no pun intended), cleaning up executor and observer");
+ Log.d(mTag, "Close called (no pun intended), cleaning up executor and observer");
}
if (mIsClosed.compareAndSet(false, true)) {
// Unregister the content observer.
@@ -240,9 +260,8 @@ public class UserDictionaryLookup implements Closeable {
*
* @return true if the initial load is successful
*/
- @UsedForTesting
- boolean isLoaded() {
- return mDictWords != null;
+ public boolean isLoaded() {
+ return mDictWords != null && mShortcutsPerLocale != null;
}
/**
@@ -255,14 +274,13 @@ public class UserDictionaryLookup implements Closeable {
* @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) {
+ public boolean isValidWord(@Nonnull final String word, @Nonnull 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");
+ Log.d(mTag, "isValidWord invoked, but initial load not complete");
}
return false;
}
@@ -271,7 +289,7 @@ public class UserDictionaryLookup implements Closeable {
final HashMap<String, ArrayList<Locale>> dictWords = mDictWords;
if (DEBUG) {
- Log.d(TAG, "isValidWord invoked for word [" + word +
+ Log.d(mTag, "isValidWord invoked for word [" + word +
"] in locale " + locale);
}
// Lowercase the word using the given locale. Note, that dictionary
@@ -282,13 +300,13 @@ public class UserDictionaryLookup implements Closeable {
final ArrayList<Locale> dictLocales = dictWords.get(lowercased);
if (null == dictLocales) {
if (DEBUG) {
- Log.d(TAG, "isValidWord=false, since there is no entry for " +
+ Log.d(mTag, "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 +
+ Log.d(mTag, "isValidWord found an entry for lowercased word [" + lowercased +
"]; examining locales");
}
// Iterate over the locales this word is in.
@@ -296,28 +314,90 @@ public class UserDictionaryLookup implements Closeable {
final int matchLevel = LocaleUtils.getMatchLevel(dictLocale.toString(),
locale.toString());
if (DEBUG) {
- Log.d(TAG, "matchLevel for dictLocale=" + dictLocale + ", locale=" +
+ Log.d(mTag, "matchLevel for dictLocale=" + dictLocale + ", locale=" +
locale + " is " + matchLevel);
}
if (LocaleUtils.isMatch(matchLevel)) {
if (DEBUG) {
- Log.d(TAG, "isValidWord=true, since matchLevel " + matchLevel +
+ Log.d(mTag, "isValidWord=true, since matchLevel " + matchLevel +
" is a match");
}
return true;
}
if (DEBUG) {
- Log.d(TAG, "matchLevel " + matchLevel + " is not a match");
+ Log.d(mTag, "matchLevel " + matchLevel + " is not a match");
}
}
if (DEBUG) {
- Log.d(TAG, "isValidWord=false, since none of the locales matched");
+ Log.d(mTag, "isValidWord=false, since none of the locales matched");
}
return false;
}
}
/**
+ * Expands the given shortcut for the given locale.
+ *
+ * @param shortcut the shortcut to expand
+ * @param inputLocale the locale in which to expand the shortcut
+ * @return expanded shortcut iff the word is a shortcut in the UserDictionary.
+ */
+ @Nullable public String expandShortcut(
+ @Nonnull final String shortcut, @Nonnull final Locale inputLocale) {
+ if (DEBUG) {
+ Log.d(mTag, "expandShortcut() : Shortcut [" + shortcut + "] for [" + inputLocale + "]");
+ }
+
+ // Atomically obtain the current copy of mShortcuts;
+ final HashMap<Locale, HashMap<String, String>> shortcutsPerLocale = mShortcutsPerLocale;
+
+ // Exit as early as possible. Most users don't use shortcuts.
+ if (CollectionUtils.isNullOrEmpty(shortcutsPerLocale)) {
+ return null;
+ }
+
+ if (!TextUtils.isEmpty(inputLocale.getCountry())) {
+ // First look for the country-specific shortcut: en_US, en_UK, fr_FR, etc.
+ final String expansionForCountry = expandShortcut(
+ shortcutsPerLocale, shortcut, inputLocale);
+ if (!TextUtils.isEmpty(expansionForCountry)) {
+ return expansionForCountry;
+ }
+ }
+
+ // Next look for the language-specific shortcut: en, fr, etc.
+ final Locale languageOnlyLocale =
+ LocaleUtils.constructLocaleFromString(inputLocale.getLanguage());
+ final String expansionForLanguage = expandShortcut(
+ shortcutsPerLocale, shortcut, languageOnlyLocale);
+ if (!TextUtils.isEmpty(expansionForLanguage)) {
+ return expansionForLanguage;
+ }
+
+ // If all else fails, loof for a global shortcut.
+ return expandShortcut(shortcutsPerLocale, shortcut, ANY_LOCALE);
+ }
+
+ @Nullable private String expandShortcut(
+ @Nullable final HashMap<Locale, HashMap<String, String>> shortcutsPerLocale,
+ @Nonnull final String shortcut,
+ @Nonnull final Locale locale) {
+ if (CollectionUtils.isNullOrEmpty(shortcutsPerLocale)) {
+ return null;
+ }
+ final HashMap<String, String> localeShortcuts = shortcutsPerLocale.get(locale);
+ if (CollectionUtils.isNullOrEmpty(localeShortcuts)) {
+ return null;
+ }
+ final String word = localeShortcuts.get(shortcut);
+ if (DEBUG && word != null) {
+ Log.d(mTag, "expandShortcut() : Shortcut [" + shortcut + "] for [" + locale
+ + "] expands to [" + word + "]");
+ }
+ return word;
+ }
+
+ /**
* Loads the UserDictionary in the current thread.
*
* Only one reload can happen at a time. If already running, will exit quickly.
@@ -325,45 +405,36 @@ public class UserDictionaryLookup implements Closeable {
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");
- }
+ Log.i(mTag, "loadUserDictionary() : Already Loading (exit)");
return;
}
- if (DEBUG) {
- Log.d(TAG, "Loading UserDictionary");
- }
+ Log.i(mTag, "loadUserDictionary() : Start Loading");
HashMap<String, ArrayList<Locale>> dictWords = new HashMap<>();
+ HashMap<Locale, HashMap<String, String>> shortcutsPerLocale = new HashMap<>();
// 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");
- }
+ Log.i(mTag, "loadUserDictionary() : Empty");
} 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);
+ final int dictLocaleIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
if (dictLocaleIndex < 0) {
if (DEBUG) {
- Log.d(TAG, "Encountered UserDictionary entry " +
- "without LOCALE, skipping");
+ Log.d(mTag, "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);
+ final int dictWordIndex = cursor.getColumnIndex(UserDictionary.Words.WORD);
if (dictWordIndex < 0) {
if (DEBUG) {
- Log.d(TAG, "Encountered UserDictionary entry without " +
- "WORD, skipping");
+ Log.d(mTag, "Encountered UserDictionary entry without WORD, skipping");
}
continue;
}
@@ -371,7 +442,7 @@ public class UserDictionaryLookup implements Closeable {
final String rawDictWord = cursor.getString(dictWordIndex);
if (null == rawDictWord) {
if (DEBUG) {
- Log.d(TAG, "Encountered null word");
+ Log.d(mTag, "Encountered null word");
}
continue;
}
@@ -380,19 +451,17 @@ public class UserDictionaryLookup implements Closeable {
String localeString = cursor.getString(dictLocaleIndex);
if (null == localeString) {
if (DEBUG) {
- Log.d(TAG, "Encountered null locale for word [" +
+ Log.d(mTag, "Encountered null locale for word [" +
rawDictWord + "], assuming all locales");
}
- // For purposes of LocaleUtils, an empty locale matches
- // everything.
+ // For purposes of LocaleUtils, an empty locale matches everything.
localeString = "";
}
- final Locale dictLocale = LocaleUtils.constructLocaleFromString(
- 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 +
+ Log.d(mTag, "Incorporating UserDictionary word [" + dictWord +
"] for locale " + dictLocale);
}
// Check if there is an existing entry for this word.
@@ -400,7 +469,7 @@ public class UserDictionaryLookup implements Closeable {
if (null == dictLocales) {
// If there is no entry for this word, create one.
if (DEBUG) {
- Log.d(TAG, "Word [" + dictWord +
+ Log.d(mTag, "Word [" + dictWord +
"] not seen for other locales, creating new entry");
}
dictLocales = new ArrayList<>();
@@ -408,13 +477,42 @@ public class UserDictionaryLookup implements Closeable {
}
// Append the locale to the list of locales this word is in.
dictLocales.add(dictLocale);
+
+ // If there is no column for a shortcut, we're done.
+ final int shortcutIndex = cursor.getColumnIndex(UserDictionary.Words.SHORTCUT);
+ if (shortcutIndex < 0) {
+ if (DEBUG) {
+ Log.d(mTag, "Encountered UserDictionary entry without SHORTCUT, done");
+ }
+ continue;
+ }
+ // If the shortcut is null, we're done.
+ final String shortcut = cursor.getString(shortcutIndex);
+ if (shortcut == null) {
+ if (DEBUG) {
+ Log.d(mTag, "Encountered null shortcut");
+ }
+ continue;
+ }
+ // Else, save the shortcut.
+ HashMap<String, String> localeShortcuts = shortcutsPerLocale.get(dictLocale);
+ if (localeShortcuts == null) {
+ localeShortcuts = new HashMap<>();
+ shortcutsPerLocale.put(dictLocale, localeShortcuts);
+ }
+ // Map to the raw input, which might be capitalized.
+ // This lets the user create a shortcut from "gm" to "General Motors".
+ localeShortcuts.put(shortcut, rawDictWord);
}
}
- // Atomically replace the copy of mDictWords.
+ // Atomically replace the copy of mDictWords and mShortcuts.
mDictWords = dictWords;
+ mShortcutsPerLocale = shortcutsPerLocale;
// Allow other calls to loadUserDictionary to execute now.
mIsLoading.set(false);
+
+ Log.i(mTag, "loadUserDictionary() : Loaded " + mDictWords.size() + " words");
}
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index cf4064b72..9ceb37145 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -104,6 +104,10 @@ public final class InputLogic {
private boolean mIsAutoCorrectionIndicatorOn;
private long mDoubleSpacePeriodCountdownStart;
+ // The word being corrected while the cursor is in the middle of the word.
+ // Note: This does not have a composing span, so it must be handled separately.
+ private String mWordBeingCorrectedByCursor = null;
+
/**
* Create a new instance of the input logic.
* @param latinIME the instance of the parent LatinIME. We should remove this when we can.
@@ -133,6 +137,7 @@ public final class InputLogic {
*/
public void startInput(final String combiningSpec, final SettingsValues settingsValues) {
mEnteredText = null;
+ mWordBeingCorrectedByCursor = null;
if (!mWordComposer.getTypedWord().isEmpty()) {
// For messaging apps that offer send button, the IME does not get the opportunity
// to capture the last word. This block should capture those uncommitted words.
@@ -247,6 +252,7 @@ public final class InputLogic {
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.NONE;
mEnteredText = text;
+ mWordBeingCorrectedByCursor = null;
inputTransaction.setDidAffectContents();
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
return inputTransaction;
@@ -386,6 +392,15 @@ public final class InputLogic {
// it here, which means we'll keep outdated suggestions for a split second but the
// visual result is better.
resetEntireInputState(newSelStart, newSelEnd, false /* clearSuggestionStrip */);
+ // If the user is in the middle of correcting a word, we should learn it before moving
+ // the cursor away.
+ if (!TextUtils.isEmpty(mWordBeingCorrectedByCursor)) {
+ final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
+ System.currentTimeMillis());
+ mDictionaryFacilitator.addToUserHistory(mWordBeingCorrectedByCursor, false,
+ NgramContext.EMPTY_PREV_WORDS_INFO, timeStampInSeconds,
+ settingsValues.mBlockPotentiallyOffensive);
+ }
} else {
// resetEntireInputState calls resetCachesUponCursorMove, but forcing the
// composition to end. But in all cases where we don't reset the entire input
@@ -401,6 +416,7 @@ public final class InputLogic {
mLatinIME.mHandler.postResumeSuggestions(true /* shouldDelay */);
// Stop the last recapitalization, if started.
mRecapitalizeStatus.stop();
+ mWordBeingCorrectedByCursor = null;
return true;
}
@@ -420,6 +436,7 @@ public final class InputLogic {
public InputTransaction onCodeInput(final SettingsValues settingsValues,
@Nonnull final Event event, final int keyboardShiftMode,
final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {
+ mWordBeingCorrectedByCursor = null;
final Event processedEvent = mWordComposer.processEvent(event);
final InputTransaction inputTransaction = new InputTransaction(settingsValues,
processedEvent, SystemClock.uptimeMillis(), mSpaceState,
@@ -453,6 +470,14 @@ public final class InputLogic {
}
currentEvent = currentEvent.mNextEvent;
}
+ // Try to record the word being corrected when the user enters a word character or
+ // the backspace key.
+ if (!mWordComposer.isComposingWord()
+ && (settingsValues.isWordCodePoint(processedEvent.mCodePoint) ||
+ processedEvent.mKeyCode == Constants.CODE_DELETE)) {
+ mWordBeingCorrectedByCursor = getWordAtCursor(
+ settingsValues, currentKeyboardScriptId);
+ }
if (!inputTransaction.didAutoCorrect() && processedEvent.mKeyCode != Constants.CODE_SHIFT
&& processedEvent.mKeyCode != Constants.CODE_CAPSLOCK
&& processedEvent.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
@@ -466,6 +491,7 @@ public final class InputLogic {
public void onStartBatchInput(final SettingsValues settingsValues,
final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+ mWordBeingCorrectedByCursor = null;
mInputLogicHandler.onStartBatchInput();
handler.showGesturePreviewAndSuggestionStrip(
SuggestedWords.getEmptyInstance(), false /* dismissGestureFloatingPreviewText */);
@@ -1151,27 +1177,30 @@ public final class InputLogic {
}
}
- boolean unlearnWordBeingDeleted(
- final SettingsValues settingsValues,final int currentKeyboardScriptId) {
- // If we just started backspacing to delete a previous word (but have not
- // entered the composing state yet), unlearn the word.
- // TODO: Consider tracking whether or not this word was typed by the user.
+ String getWordAtCursor(final SettingsValues settingsValues, final int currentKeyboardScriptId) {
if (!mConnection.hasSelection()
&& settingsValues.isSuggestionsEnabledPerUserSettings()
- && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
- && !mConnection.isCursorFollowedByWordCharacter(
- settingsValues.mSpacingAndPunctuations)) {
+ && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) {
final TextRange range = mConnection.getWordRangeAtCursor(
settingsValues.mSpacingAndPunctuations,
currentKeyboardScriptId);
- if (range == null) {
- // Happens if we don't have an input connection at all.
- return false;
+ if (range != null) {
+ return range.mWord.toString();
}
- final String wordBeingDeleted = range.mWord.toString();
- if (!wordBeingDeleted.isEmpty()) {
- unlearnWord(wordBeingDeleted, settingsValues,
- Constants.EVENT_BACKSPACE);
+ }
+ return "";
+ }
+
+ boolean unlearnWordBeingDeleted(
+ final SettingsValues settingsValues, final int currentKeyboardScriptId) {
+ // If we just started backspacing to delete a previous word (but have not
+ // entered the composing state yet), unlearn the word.
+ // TODO: Consider tracking whether or not this word was typed by the user.
+ if (!mConnection.isCursorFollowedByWordCharacter(settingsValues.mSpacingAndPunctuations)) {
+ final String wordBeingDeleted = getWordAtCursor(
+ settingsValues, currentKeyboardScriptId);
+ if (!TextUtils.isEmpty(wordBeingDeleted)) {
+ unlearnWord(wordBeingDeleted, settingsValues, Constants.EVENT_BACKSPACE);
return true;
}
}
@@ -1407,7 +1436,7 @@ public final class InputLogic {
return;
}
- final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>();
+ final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>("Suggest");
mInputLogicHandler.getSuggestedWords(inputStyle, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
new OnGetSuggestedWordsCallback() {
@Override
@@ -2075,24 +2104,60 @@ public final class InputLogic {
*/
private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord,
final int commitType, final String separatorString) {
+ long startTimeMillis = 0;
+ if (DebugFlags.DEBUG_ENABLED) {
+ startTimeMillis = System.currentTimeMillis();
+ Log.d(TAG, "commitChosenWord() : [" + chosenWord + "]");
+ }
final SuggestedWords suggestedWords = mSuggestedWords;
final CharSequence chosenWordWithSuggestions =
SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
suggestedWords);
+ if (DebugFlags.DEBUG_ENABLED) {
+ long runTimeMillis = System.currentTimeMillis() - startTimeMillis;
+ Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run "
+ + "SuggestionSpanUtils.getTextWithSuggestionSpan()");
+ startTimeMillis = System.currentTimeMillis();
+ }
// When we are composing word, get n-gram context from the 2nd previous word because the
// 1st previous word is the word to be committed. Otherwise get n-gram context from the 1st
// previous word.
final NgramContext ngramContext = mConnection.getNgramContextFromNthPreviousWord(
settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1);
+ if (DebugFlags.DEBUG_ENABLED) {
+ long runTimeMillis = System.currentTimeMillis() - startTimeMillis;
+ Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run "
+ + "Connection.getNgramContextFromNthPreviousWord()");
+ Log.d(TAG, "commitChosenWord() : NgramContext = " + ngramContext);
+ startTimeMillis = System.currentTimeMillis();
+ }
mConnection.commitText(chosenWordWithSuggestions, 1);
+ if (DebugFlags.DEBUG_ENABLED) {
+ long runTimeMillis = System.currentTimeMillis() - startTimeMillis;
+ Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run "
+ + "Connection.commitText");
+ startTimeMillis = System.currentTimeMillis();
+ }
// Add the word to the user history dictionary
performAdditionToUserHistoryDictionary(settingsValues, chosenWord, ngramContext);
+ if (DebugFlags.DEBUG_ENABLED) {
+ long runTimeMillis = System.currentTimeMillis() - startTimeMillis;
+ Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run "
+ + "performAdditionToUserHistoryDictionary()");
+ startTimeMillis = System.currentTimeMillis();
+ }
// TODO: figure out here if this is an auto-correct or if the best word is actually
// what user typed. Note: currently this is done much later in
// LastComposedWord#didCommitTypedWord by string equality of the remembered
// strings.
mLastComposedWord = mWordComposer.commitWord(commitType,
chosenWordWithSuggestions, separatorString, ngramContext);
+ if (DebugFlags.DEBUG_ENABLED) {
+ long runTimeMillis = System.currentTimeMillis() - startTimeMillis;
+ Log.d(TAG, "commitChosenWord() : " + runTimeMillis + " ms to run "
+ + "WordComposer.commitWord()");
+ startTimeMillis = System.currentTimeMillis();
+ }
}
/**
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index d112e7200..94573a6d5 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -209,7 +209,7 @@ public class SettingsValues {
prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE,
defaultKeyPreviewDismissEndScale);
mDisplayOrientation = res.getConfiguration().orientation;
- mAppWorkarounds = new AsyncResultHolder<>();
+ mAppWorkarounds = new AsyncResultHolder<>("AppWorkarounds");
final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo(
mInputAttributes.mTargetApplicationPackageName);
if (null != packageInfo) {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 766b385a9..4625e8e8b 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -24,7 +24,6 @@ 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;
@@ -83,7 +82,6 @@ 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,30 +93,11 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
@Override
public void onCreate() {
super.onCreate();
- mRecommendedThreshold =
- Float.parseFloat(getString(R.string.spellchecker_recommended_threshold_value));
+ mRecommendedThreshold = Float.parseFloat(
+ getString(R.string.spellchecker_recommended_threshold_value));
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() {
@@ -181,16 +160,6 @@ 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/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
index 952ac2a62..1525f2d56 100644
--- a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
+++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin.utils;
+import android.util.Log;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -29,9 +31,11 @@ public class AsyncResultHolder<E> {
private final Object mLock = new Object();
private E mResult;
+ private final String mTag;
private final CountDownLatch mLatch;
- public AsyncResultHolder() {
+ public AsyncResultHolder(final String tag) {
+ mTag = tag;
mLatch = new CountDownLatch(1);
}
@@ -61,6 +65,7 @@ public class AsyncResultHolder<E> {
try {
return mLatch.await(timeOut, TimeUnit.MILLISECONDS) ? mResult : defaultValue;
} catch (InterruptedException e) {
+ Log.w(mTag, "get() : Interrupted after " + timeOut + " ms");
return defaultValue;
}
}
diff --git a/tests/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookupTest.java b/tests/src/com/android/inputmethod/latin/UserDictionaryLookupTest.java
index e5c813942..d8060f286 100644
--- a/tests/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookupTest.java
+++ b/tests/src/com/android/inputmethod/latin/UserDictionaryLookupTest.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin.spellcheck;
+package com.android.inputmethod.latin;
+
+import static com.android.inputmethod.latin.UserDictionaryLookup.ANY_LOCALE;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
@@ -25,11 +27,13 @@ import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
+
import java.util.HashSet;
import java.util.Locale;
/**
- * Unit tests for {@link UserDictionaryLookup}.
+ * Unit tests for {@link com.android.inputmethod.latin.UserDictionaryLookup}.
*
* Note, this test doesn't mock out the ContentResolver, in order to make sure UserDictionaryLookup
* works in a real setting.
@@ -68,9 +72,9 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
* @return the Uri for the given word
*/
@SuppressLint("NewApi")
- private Uri addWord(final String word, final Locale locale, int frequency) {
+ private Uri addWord(final String word, final Locale locale, int frequency, String shortcut) {
// Add the given word for the given locale.
- UserDictionary.Words.addWord(mContext, word, frequency, null, locale);
+ UserDictionary.Words.addWord(mContext, word, frequency, shortcut, locale);
// Obtain an Uri for the given word.
Cursor cursor = mContentResolver.query(UserDictionary.Words.CONTENT_URI, null,
UserDictionary.Words.WORD + "='" + word + "'", null, null);
@@ -94,14 +98,78 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
mContentResolver.delete(uri, null, null);
}
+ private UserDictionaryLookup setUpShortcut(final Locale locale) {
+ // Insert "shortcut" => "Expansion" in the UserDictionary for the given locale.
+ addWord("Expansion", locale, 17, "shortcut");
+
+ // Create the UserDictionaryLookup and wait until it's loaded.
+ UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
+ lookup.open();
+ while (!lookup.isLoaded()) {
+ }
+ return lookup;
+ }
+
+ public void testShortcutKeyMatching() {
+ Log.d(TAG, "testShortcutKeyMatching");
+ UserDictionaryLookup lookup = setUpShortcut(Locale.US);
+
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
+ assertNull(lookup.expandShortcut("Shortcut", Locale.US));
+ assertNull(lookup.expandShortcut("SHORTCUT", Locale.US));
+ assertNull(lookup.expandShortcut("shortcu", Locale.US));
+ assertNull(lookup.expandShortcut("shortcutt", Locale.US));
+
+ lookup.close();
+ }
+
+ public void testShortcutMatchesInputCountry() {
+ Log.d(TAG, "testShortcutMatchesInputCountry");
+ UserDictionaryLookup lookup = setUpShortcut(Locale.US);
+
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
+ assertNull(lookup.expandShortcut("shortcut", Locale.UK));
+ assertNull(lookup.expandShortcut("shortcut", Locale.ENGLISH));
+ assertNull(lookup.expandShortcut("shortcut", Locale.FRENCH));
+ assertNull(lookup.expandShortcut("shortcut", ANY_LOCALE));
+
+ lookup.close();
+ }
+
+ public void testShortcutMatchesInputLanguage() {
+ Log.d(TAG, "testShortcutMatchesInputLanguage");
+ UserDictionaryLookup lookup = setUpShortcut(Locale.ENGLISH);
+
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK));
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.ENGLISH));
+ assertNull(lookup.expandShortcut("shortcut", Locale.FRENCH));
+ assertNull(lookup.expandShortcut("shortcut", ANY_LOCALE));
+
+ lookup.close();
+ }
+
+ public void testShortcutMatchesAnyLocale() {
+ UserDictionaryLookup lookup = setUpShortcut(UserDictionaryLookup.ANY_LOCALE);
+
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK));
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.ENGLISH));
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.FRENCH));
+ assertEquals("Expansion", lookup.expandShortcut("shortcut", ANY_LOCALE));
+
+ lookup.close();
+ }
+
public void testExactLocaleMatch() {
Log.d(TAG, "testExactLocaleMatch");
// Insert "Foo" as capitalized in the UserDictionary under en_US locale.
- addWord("Foo", Locale.US, 17);
+ addWord("Foo", Locale.US, 17, null);
// Create the UserDictionaryLookup and wait until it's loaded.
- UserDictionaryLookup lookup = new UserDictionaryLookup(mContext);
+ UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
+ lookup.open();
while (!lookup.isLoaded()) {
}
@@ -117,7 +185,7 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
assertFalse(lookup.isValidWord("foo", Locale.ENGLISH));
assertFalse(lookup.isValidWord("foo", Locale.UK));
assertFalse(lookup.isValidWord("foo", Locale.FRENCH));
- assertFalse(lookup.isValidWord("foo", new Locale("")));
+ assertFalse(lookup.isValidWord("foo", ANY_LOCALE));
lookup.close();
}
@@ -126,10 +194,11 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
Log.d(TAG, "testSubLocaleMatch");
// Insert "Foo" as capitalized in the UserDictionary under the en locale.
- addWord("Foo", Locale.ENGLISH, 17);
+ addWord("Foo", Locale.ENGLISH, 17, null);
// Create the UserDictionaryLookup and wait until it's loaded.
- UserDictionaryLookup lookup = new UserDictionaryLookup(mContext);
+ UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
+ lookup.open();
while (!lookup.isLoaded()) {
}
@@ -150,15 +219,16 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
Log.d(TAG, "testAllLocalesMatch");
// Insert "Foo" as capitalized in the UserDictionary under the all locales.
- addWord("Foo", null, 17);
+ addWord("Foo", null, 17, null);
// Create the UserDictionaryLookup and wait until it's loaded.
- UserDictionaryLookup lookup = new UserDictionaryLookup(mContext);
+ UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
+ lookup.open();
while (!lookup.isLoaded()) {
}
// Any capitalization variation should match for fr, en and en_US.
- assertTrue(lookup.isValidWord("foo", new Locale("")));
+ assertTrue(lookup.isValidWord("foo", ANY_LOCALE));
assertTrue(lookup.isValidWord("foo", Locale.FRENCH));
assertTrue(lookup.isValidWord("foo", Locale.ENGLISH));
assertTrue(lookup.isValidWord("foo", Locale.US));
@@ -177,12 +247,13 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
// Insert "Foo" as capitalized in the UserDictionary under the en_US and en_CA and fr
// locales.
- addWord("Foo", Locale.US, 17);
- addWord("foO", Locale.CANADA, 17);
- addWord("fOo", Locale.FRENCH, 17);
+ addWord("Foo", Locale.US, 17, null);
+ addWord("foO", Locale.CANADA, 17, null);
+ addWord("fOo", Locale.FRENCH, 17, null);
// Create the UserDictionaryLookup and wait until it's loaded.
- UserDictionaryLookup lookup = new UserDictionaryLookup(mContext);
+ UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
+ lookup.open();
while (!lookup.isLoaded()) {
}
@@ -193,7 +264,7 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
// Other locales, including more general locales won't match.
assertFalse(lookup.isValidWord("foo", Locale.ENGLISH));
assertFalse(lookup.isValidWord("foo", Locale.UK));
- assertFalse(lookup.isValidWord("foo", new Locale("")));
+ assertFalse(lookup.isValidWord("foo", ANY_LOCALE));
lookup.close();
}
@@ -202,10 +273,11 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
Log.d(TAG, "testReload");
// Insert "foo".
- Uri uri = addWord("foo", Locale.US, 17);
+ Uri uri = addWord("foo", Locale.US, 17, null);
// Create the UserDictionaryLookup and wait until it's loaded.
- UserDictionaryLookup lookup = new UserDictionaryLookup(mContext);
+ UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
+ lookup.open();
while (!lookup.isLoaded()) {
}
@@ -217,7 +289,7 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
// Now delete "foo" and add "bar".
deleteWord(uri);
- addWord("bar", Locale.US, 18);
+ addWord("bar", Locale.US, 18, null);
// Wait a little bit before expecting a change. The time we wait should be greater than
// UserDictionaryLookup.RELOAD_DELAY_MS.
@@ -241,10 +313,11 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
Log.d(TAG, "testClose");
// Insert "foo".
- Uri uri = addWord("foo", Locale.US, 17);
+ Uri uri = addWord("foo", Locale.US, 17, null);
// Create the UserDictionaryLookup and wait until it's loaded.
- UserDictionaryLookup lookup = new UserDictionaryLookup(mContext);
+ UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
+ lookup.open();
while (!lookup.isLoaded()) {
}
@@ -259,7 +332,7 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
// Now delete "foo" and add "bar".
deleteWord(uri);
- addWord("bar", Locale.US, 18);
+ addWord("bar", Locale.US, 18, null);
// Wait a little bit before expecting a change. The time we wait should be greater than
// UserDictionaryLookup.RELOAD_DELAY_MS.
diff --git a/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java b/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java
index 170d64383..c214b5fd0 100644
--- a/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/AsyncResultHolderTests.java
@@ -45,27 +45,27 @@ public class AsyncResultHolderTests extends AndroidTestCase {
}
public void testGetWithoutSet() {
- final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>();
+ final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>("Test");
final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
assertEquals(DEFAULT_VALUE, resultValue);
}
public void testGetBeforeSet() {
- final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>();
+ final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>("Test");
setAfterGivenTime(holder, SET_VALUE, TIMEOUT_IN_MILLISECONDS + MARGIN_IN_MILLISECONDS);
final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
assertEquals(DEFAULT_VALUE, resultValue);
}
public void testGetAfterSet() {
- final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>();
+ final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>("Test");
holder.set(SET_VALUE);
final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
assertEquals(SET_VALUE, resultValue);
}
public void testGetBeforeTimeout() {
- final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>();
+ final AsyncResultHolder<Integer> holder = new AsyncResultHolder<>("Test");
setAfterGivenTime(holder, SET_VALUE, TIMEOUT_IN_MILLISECONDS - MARGIN_IN_MILLISECONDS);
final int resultValue = holder.get(DEFAULT_VALUE, TIMEOUT_IN_MILLISECONDS);
assertEquals(SET_VALUE, resultValue);
diff --git a/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java
index 47fd5feaa..6871a41d5 100644
--- a/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/CollectionUtilsTests.java
@@ -24,6 +24,9 @@ import com.android.inputmethod.latin.common.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* Tests for {@link CollectionUtils}.
@@ -79,9 +82,13 @@ public class CollectionUtilsTests extends AndroidTestCase {
* results for a few cases.
*/
public void testIsNullOrEmpty() {
- assertTrue(CollectionUtils.isNullOrEmpty(null));
- assertTrue(CollectionUtils.isNullOrEmpty(new ArrayList<>()));
- assertTrue(CollectionUtils.isNullOrEmpty(Collections.EMPTY_SET));
- assertFalse(CollectionUtils.isNullOrEmpty(Collections.singleton("Not empty")));
+ assertTrue(CollectionUtils.isNullOrEmpty((List) null));
+ assertTrue(CollectionUtils.isNullOrEmpty((Map) null));
+ assertTrue(CollectionUtils.isNullOrEmpty(new ArrayList()));
+ assertTrue(CollectionUtils.isNullOrEmpty(new HashMap()));
+ assertTrue(CollectionUtils.isNullOrEmpty(Collections.EMPTY_LIST));
+ assertTrue(CollectionUtils.isNullOrEmpty(Collections.EMPTY_MAP));
+ assertFalse(CollectionUtils.isNullOrEmpty(Collections.singletonList("Not empty")));
+ assertFalse(CollectionUtils.isNullOrEmpty(Collections.singletonMap("Not", "empty")));
}
}