Merge "Personal dictionary feeds a personal LM."
This commit is contained in:
commit
40f0f61bb3
8 changed files with 371 additions and 225 deletions
|
@ -24,6 +24,7 @@ import android.provider.ContactsContract.Contacts;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
|
import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
|
||||||
|
import com.android.inputmethod.latin.define.DebugFlags;
|
||||||
import com.android.inputmethod.latin.utils.ExecutorUtils;
|
import com.android.inputmethod.latin.utils.ExecutorUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -33,8 +34,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
* A content observer that listens to updates to content provider {@link Contacts#CONTENT_URI}.
|
* A content observer that listens to updates to content provider {@link Contacts#CONTENT_URI}.
|
||||||
*/
|
*/
|
||||||
public class ContactsContentObserver implements Runnable {
|
public class ContactsContentObserver implements Runnable {
|
||||||
private static final String TAG = ContactsContentObserver.class.getSimpleName();
|
private static final String TAG = "ContactsContentObserver";
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
private static AtomicBoolean sRunning = new AtomicBoolean(false);
|
private static AtomicBoolean sRunning = new AtomicBoolean(false);
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
@ -49,8 +49,8 @@ public class ContactsContentObserver implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerObserver(final ContactsChangedListener listener) {
|
public void registerObserver(final ContactsChangedListener listener) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(TAG, "Registered Contacts Content Observer");
|
Log.d(TAG, "registerObserver()");
|
||||||
}
|
}
|
||||||
mContactsChangedListener = listener;
|
mContactsChangedListener = listener;
|
||||||
mContentObserver = new ContentObserver(null /* handler */) {
|
mContentObserver = new ContentObserver(null /* handler */) {
|
||||||
|
@ -67,13 +67,13 @@ public class ContactsContentObserver implements Runnable {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!sRunning.compareAndSet(false /* expect */, true /* update */)) {
|
if (!sRunning.compareAndSet(false /* expect */, true /* update */)) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(TAG, "run() : Already running. Don't waste time checking again.");
|
Log.d(TAG, "run() : Already running. Don't waste time checking again.");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (haveContentsChanged()) {
|
if (haveContentsChanged()) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(TAG, "run() : Contacts have changed. Notifying listeners.");
|
Log.d(TAG, "run() : Contacts have changed. Notifying listeners.");
|
||||||
}
|
}
|
||||||
mContactsChangedListener.onContactsChange();
|
mContactsChangedListener.onContactsChange();
|
||||||
|
@ -91,9 +91,9 @@ public class ContactsContentObserver implements Runnable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (contactCount != mManager.getContactCountAtLastRebuild()) {
|
if (contactCount != mManager.getContactCountAtLastRebuild()) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(TAG, "Contact count changed: " + mManager.getContactCountAtLastRebuild()
|
Log.d(TAG, "haveContentsChanged() : Count changed from "
|
||||||
+ " to " + contactCount);
|
+ mManager.getContactCountAtLastRebuild() + " to " + contactCount);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -101,9 +101,9 @@ public class ContactsContentObserver implements Runnable {
|
||||||
if (names.hashCode() != mManager.getHashCodeAtLastRebuild()) {
|
if (names.hashCode() != mManager.getHashCodeAtLastRebuild()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(TAG, "No contacts changed. (runtime = " + (SystemClock.uptimeMillis() - startTime)
|
Log.d(TAG, "haveContentsChanged() : No change detected in "
|
||||||
+ " ms)");
|
+ (SystemClock.uptimeMillis() - startTime) + " ms)");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
* measure of the current state of the content provider.
|
* measure of the current state of the content provider.
|
||||||
*/
|
*/
|
||||||
public class ContactsManager {
|
public class ContactsManager {
|
||||||
private static final String TAG = ContactsManager.class.getSimpleName();
|
private static final String TAG = "ContactsManager";
|
||||||
private static final boolean DEBUG = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to implement for classes interested in getting notified for updates
|
* Interface to implement for classes interested in getting notified for updates
|
||||||
|
|
|
@ -45,31 +45,14 @@ public interface DictionaryFacilitator {
|
||||||
|
|
||||||
public static final String[] ALL_DICTIONARY_TYPES = new String[] {
|
public static final String[] ALL_DICTIONARY_TYPES = new String[] {
|
||||||
Dictionary.TYPE_MAIN,
|
Dictionary.TYPE_MAIN,
|
||||||
|
Dictionary.TYPE_CONTACTS,
|
||||||
Dictionary.TYPE_USER_HISTORY,
|
Dictionary.TYPE_USER_HISTORY,
|
||||||
Dictionary.TYPE_USER,
|
Dictionary.TYPE_USER};
|
||||||
Dictionary.TYPE_CONTACTS};
|
|
||||||
|
|
||||||
public static final String[] DYNAMIC_DICTIONARY_TYPES = new String[] {
|
public static final String[] DYNAMIC_DICTIONARY_TYPES = new String[] {
|
||||||
|
Dictionary.TYPE_CONTACTS,
|
||||||
Dictionary.TYPE_USER_HISTORY,
|
Dictionary.TYPE_USER_HISTORY,
|
||||||
Dictionary.TYPE_USER,
|
Dictionary.TYPE_USER};
|
||||||
Dictionary.TYPE_CONTACTS};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Dictionary#TYPE_USER} is deprecated, except for the spelling service.
|
|
||||||
*/
|
|
||||||
public static final String[] DICTIONARY_TYPES_FOR_SPELLING = new String[] {
|
|
||||||
Dictionary.TYPE_MAIN,
|
|
||||||
Dictionary.TYPE_USER_HISTORY,
|
|
||||||
Dictionary.TYPE_USER,
|
|
||||||
Dictionary.TYPE_CONTACTS};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Dictionary#TYPE_USER} is deprecated, except for the spelling service.
|
|
||||||
*/
|
|
||||||
public static final String[] DICTIONARY_TYPES_FOR_SUGGESTIONS = new String[] {
|
|
||||||
Dictionary.TYPE_MAIN,
|
|
||||||
Dictionary.TYPE_USER_HISTORY,
|
|
||||||
Dictionary.TYPE_CONTACTS};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this facilitator is exactly for this locale.
|
* Returns whether this facilitator is exactly for this locale.
|
||||||
|
|
|
@ -557,7 +557,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
false /* firstSuggestionExceedsConfidenceThreshold */);
|
false /* firstSuggestionExceedsConfidenceThreshold */);
|
||||||
final float[] weightOfLangModelVsSpatialModel =
|
final float[] weightOfLangModelVsSpatialModel =
|
||||||
new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
|
new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
|
||||||
for (final String dictType : DICTIONARY_TYPES_FOR_SUGGESTIONS) {
|
for (final String dictType : ALL_DICTIONARY_TYPES) {
|
||||||
final Dictionary dictionary = mDictionaryGroup.getDict(dictType);
|
final Dictionary dictionary = mDictionaryGroup.getDict(dictType);
|
||||||
if (null == dictionary) continue;
|
if (null == dictionary) continue;
|
||||||
final float weightForLocale = composedData.mIsBatchMode
|
final float weightForLocale = composedData.mIsBatchMode
|
||||||
|
@ -577,11 +577,11 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidSpellingWord(final String word) {
|
public boolean isValidSpellingWord(final String word) {
|
||||||
return isValidWord(word, DICTIONARY_TYPES_FOR_SPELLING);
|
return isValidWord(word, ALL_DICTIONARY_TYPES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidSuggestionWord(final String word) {
|
public boolean isValidSuggestionWord(final String word) {
|
||||||
return isValidWord(word, DICTIONARY_TYPES_FOR_SUGGESTIONS);
|
return isValidWord(word, ALL_DICTIONARY_TYPES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidWord(final String word, final String[] dictionariesToCheck) {
|
private boolean isValidWord(final String word, final String[] dictionariesToCheck) {
|
||||||
|
|
|
@ -28,13 +28,18 @@ import android.util.Log;
|
||||||
import com.android.inputmethod.annotations.UsedForTesting;
|
import com.android.inputmethod.annotations.UsedForTesting;
|
||||||
import com.android.inputmethod.latin.common.CollectionUtils;
|
import com.android.inputmethod.latin.common.CollectionUtils;
|
||||||
import com.android.inputmethod.latin.common.LocaleUtils;
|
import com.android.inputmethod.latin.common.LocaleUtils;
|
||||||
|
import com.android.inputmethod.latin.define.DebugFlags;
|
||||||
import com.android.inputmethod.latin.utils.ExecutorUtils;
|
import com.android.inputmethod.latin.utils.ExecutorUtils;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -43,21 +48,22 @@ import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UserDictionaryLookup provides the ability to lookup into the system-wide "Personal dictionary".
|
* This class provides the ability to look into the system-wide "Personal dictionary". It loads the
|
||||||
|
* data once when created and reloads it when notified of changes to {@link UserDictionary}
|
||||||
|
*
|
||||||
|
* It can be used directly to validate words or expand shortcuts, and it can be used by instances
|
||||||
|
* of {@link PersonalLanguageModelHelper} that create language model files for a specific input
|
||||||
|
* locale.
|
||||||
*
|
*
|
||||||
* Note, that the initial dictionary loading happens asynchronously so it is possible (hopefully
|
* Note, that the initial dictionary loading happens asynchronously so it is possible (hopefully
|
||||||
* rarely) that isValidWord is called before the initial load has started.
|
* rarely) that {@link #isValidWord} or {@link #expandShortcut} is called before the initial load
|
||||||
|
* has started.
|
||||||
*
|
*
|
||||||
* The caller should explicitly call close() when the object is no longer needed, in order to
|
* The caller should explicitly call {@link #close} when the object is no longer needed, in order
|
||||||
* release any resources and references to this object. A service should create this object in
|
* to release any resources and references to this object. A service should create this object in
|
||||||
* onCreate and close() it in onDestroy.
|
* {@link android.app.Service#onCreate} and close it in {@link android.app.Service#onDestroy}.
|
||||||
*/
|
*/
|
||||||
public class UserDictionaryLookup implements Closeable {
|
public class PersonalDictionaryLookup implements Closeable {
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* To avoid loading too many dictionary entries in memory, we cap them at this number. If
|
||||||
|
@ -92,28 +98,42 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
private final String mServiceName;
|
private final String mServiceName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runnable that calls loadUserDictionary().
|
* Interface to implement for classes interested in getting notified of updates.
|
||||||
*/
|
*/
|
||||||
private class UserDictionaryLoader implements Runnable {
|
public static interface PersonalDictionaryListener {
|
||||||
@Override
|
public void onUpdate();
|
||||||
public void run() {
|
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(mTag, "Executing (re)load");
|
|
||||||
}
|
}
|
||||||
loadUserDictionary();
|
|
||||||
|
private final Set<PersonalDictionaryListener> mListeners = new HashSet<>();
|
||||||
|
|
||||||
|
public void addListener(@Nonnull final PersonalDictionaryListener listener) {
|
||||||
|
mListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeListener(@Nonnull final PersonalDictionaryListener listener) {
|
||||||
|
mListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast the update to all the Locale-specific language models.
|
||||||
|
*/
|
||||||
|
@UsedForTesting
|
||||||
|
void notifyListeners() {
|
||||||
|
for (PersonalDictionaryListener listener : mListeners) {
|
||||||
|
listener.onUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content observer for UserDictionary changes. It has the following properties:
|
* Content observer for changes to the personal dictionary. It has the following properties:
|
||||||
* 1. It spawns off a UserDictionary reload in another thread, after some delay.
|
* 1. It spawns off a reload in another thread, after some delay.
|
||||||
* 2. It cancels previously scheduled reloads, and only executes the latest.
|
* 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
|
* 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
|
* when the dictionary is edited through its settings UI, when sometimes multiple
|
||||||
* notifications are sent for the edited entry, but also for the entire UserDictionary).
|
* notifications are sent for the edited entry, but also for the entire dictionary).
|
||||||
*/
|
*/
|
||||||
private class UserDictionaryContentObserver extends ContentObserver {
|
private class PersonalDictionaryContentObserver extends ContentObserver implements Runnable {
|
||||||
public UserDictionaryContentObserver() {
|
public PersonalDictionaryContentObserver() {
|
||||||
super(null);
|
super(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,33 +150,40 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChange(boolean selfChange, Uri uri) {
|
public void onChange(boolean selfChange, Uri uri) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Received content observer onChange notification for URI: " + uri);
|
Log.d(mTag, "onChange() : URI = " + uri);
|
||||||
}
|
}
|
||||||
// Cancel (but don't interrupt) any pending reloads (except the initial load).
|
// Cancel (but don't interrupt) any pending reloads (except the initial load).
|
||||||
if (mReloadFuture != null && !mReloadFuture.isCancelled() &&
|
if (mReloadFuture != null && !mReloadFuture.isCancelled() &&
|
||||||
!mReloadFuture.isDone()) {
|
!mReloadFuture.isDone()) {
|
||||||
// Note, that if already cancelled or done, this will do nothing.
|
// Note, that if already cancelled or done, this will do nothing.
|
||||||
boolean isCancelled = mReloadFuture.cancel(false);
|
boolean isCancelled = mReloadFuture.cancel(false);
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
Log.d(mTag, "Successfully canceled previous reload request");
|
Log.d(mTag, "onChange() : Canceled previous reload request");
|
||||||
} else {
|
} else {
|
||||||
Log.d(mTag, "Unable to cancel previous reload request");
|
Log.d(mTag, "onChange() : Failed to cancel previous reload request");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Scheduling reload in " + RELOAD_DELAY_MS + " ms");
|
Log.d(mTag, "onChange() : Scheduling reload in " + RELOAD_DELAY_MS + " ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule a new reload after RELOAD_DELAY_MS.
|
// Schedule a new reload after RELOAD_DELAY_MS.
|
||||||
mReloadFuture = ExecutorUtils.getBackgroundExecutor(mServiceName)
|
mReloadFuture = ExecutorUtils.getBackgroundExecutor(mServiceName)
|
||||||
.schedule(new UserDictionaryLoader(), RELOAD_DELAY_MS, TimeUnit.MILLISECONDS);
|
.schedule(this, RELOAD_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
loadPersonalDictionary();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private final ContentObserver mObserver = new UserDictionaryContentObserver();
|
|
||||||
|
private final PersonalDictionaryContentObserver mPersonalDictionaryContentObserver =
|
||||||
|
new PersonalDictionaryContentObserver();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that a load is in progress, so no need for another.
|
* Indicates that a load is in progress, so no need for another.
|
||||||
|
@ -192,8 +219,10 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
/**
|
/**
|
||||||
* @param context the context from which to obtain content resolver
|
* @param context the context from which to obtain content resolver
|
||||||
*/
|
*/
|
||||||
public UserDictionaryLookup(@Nonnull final Context context, @Nonnull final String serviceName) {
|
public PersonalDictionaryLookup(
|
||||||
mTag = serviceName + ".UserDictionaryLookup";
|
@Nonnull final Context context,
|
||||||
|
@Nonnull final String serviceName) {
|
||||||
|
mTag = serviceName + ".Personal";
|
||||||
|
|
||||||
Log.i(mTag, "create()");
|
Log.i(mTag, "create()");
|
||||||
|
|
||||||
|
@ -216,12 +245,12 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
// Schedule the initial load to run immediately. It's possible that the first call to
|
// 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
|
// isValidWord occurs before the dictionary has actually loaded, so it should not
|
||||||
// assume that the dictionary has been loaded.
|
// assume that the dictionary has been loaded.
|
||||||
loadUserDictionary();
|
loadPersonalDictionary();
|
||||||
|
|
||||||
// Register the observer to be notified on changes to the UserDictionary and all individual
|
// Register the observer to be notified on changes to the personal dictionary and all
|
||||||
// items.
|
// individual items.
|
||||||
//
|
//
|
||||||
// If the user is interacting with the UserDictionary settings UI, or with the
|
// If the user is interacting with the Personal Dictionary settings UI, or with the
|
||||||
// "Add to dictionary" drop-down option, duplicate notifications will be sent for the same
|
// "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
|
// 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,
|
// separately for the entire dictionary. However, when used programmatically,
|
||||||
|
@ -229,7 +258,9 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
// receive every possible notification, and instead has throttling logic to avoid doing too
|
// receive every possible notification, and instead has throttling logic to avoid doing too
|
||||||
// many reloads.
|
// many reloads.
|
||||||
mResolver.registerContentObserver(
|
mResolver.registerContentObserver(
|
||||||
UserDictionary.Words.CONTENT_URI, true /* notifyForDescendents */, mObserver);
|
UserDictionary.Words.CONTENT_URI,
|
||||||
|
true /* notifyForDescendents */,
|
||||||
|
mPersonalDictionaryContentObserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -239,8 +270,8 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
@Override
|
@Override
|
||||||
public void finalize() throws Throwable {
|
public void finalize() throws Throwable {
|
||||||
try {
|
try {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Finalize called, calling close()");
|
Log.d(mTag, "finalize()");
|
||||||
}
|
}
|
||||||
close();
|
close();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -249,19 +280,19 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans up UserDictionaryLookup: shuts down any extra threads and unregisters the observer.
|
* Cleans up PersonalDictionaryLookup: 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
|
* 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.
|
* work, but no data will be reloaded any longer.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Close called (no pun intended), cleaning up executor and observer");
|
Log.d(mTag, "close() : Unregistering content observer");
|
||||||
}
|
}
|
||||||
if (mIsClosed.compareAndSet(false, true)) {
|
if (mIsClosed.compareAndSet(false, true)) {
|
||||||
// Unregister the content observer.
|
// Unregister the content observer.
|
||||||
mResolver.unregisterContentObserver(mObserver);
|
mResolver.unregisterContentObserver(mPersonalDictionaryContentObserver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,22 +306,95 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the given word is a valid word in the given locale based on the UserDictionary.
|
* Returns the set of words defined for the given locale and more general locales.
|
||||||
|
*
|
||||||
|
* For example, input locale en_US uses data for en_US, en, and the global dictionary.
|
||||||
|
*
|
||||||
|
* Note that this method returns expanded words, not shortcuts. Shortcuts are handled
|
||||||
|
* by {@link #getShortcutsForLocale}.
|
||||||
|
*
|
||||||
|
* @param inputLocale the locale to restrict for
|
||||||
|
* @return set of words that apply to the given locale.
|
||||||
|
*/
|
||||||
|
public Set<String> getWordsForLocale(@Nonnull final Locale inputLocale) {
|
||||||
|
final HashMap<String, ArrayList<Locale>> dictWords = mDictWords;
|
||||||
|
if (CollectionUtils.isNullOrEmpty(dictWords)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> words = new HashSet<>();
|
||||||
|
final String inputLocaleString = inputLocale.toString();
|
||||||
|
for (String word : dictWords.keySet()) {
|
||||||
|
for (Locale wordLocale : dictWords.get(word)) {
|
||||||
|
final String wordLocaleString = wordLocale.toString();
|
||||||
|
final int match = LocaleUtils.getMatchLevel(wordLocaleString, inputLocaleString);
|
||||||
|
if (LocaleUtils.isMatch(match)) {
|
||||||
|
words.add(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return words;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the set of shortcuts defined for the given locale and more general locales.
|
||||||
|
*
|
||||||
|
* For example, input locale en_US uses data for en_US, en, and the global dictionary.
|
||||||
|
*
|
||||||
|
* Note that this method returns shortcut keys, not expanded words. Words are handled
|
||||||
|
* by {@link #getWordsForLocale}.
|
||||||
|
*
|
||||||
|
* @param inputLocale the locale to restrict for
|
||||||
|
* @return set of shortcuts that apply to the given locale.
|
||||||
|
*/
|
||||||
|
public Set<String> getShortcutsForLocale(@Nonnull final Locale inputLocale) {
|
||||||
|
final Map<Locale, HashMap<String, String>> shortcutsPerLocale = mShortcutsPerLocale;
|
||||||
|
if (CollectionUtils.isNullOrEmpty(shortcutsPerLocale)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> shortcuts = new HashSet<>();
|
||||||
|
if (!TextUtils.isEmpty(inputLocale.getCountry())) {
|
||||||
|
// First look for the country-specific shortcut: en_US, en_UK, fr_FR, etc.
|
||||||
|
final Map<String, String> countryShortcuts = shortcutsPerLocale.get(inputLocale);
|
||||||
|
if (!CollectionUtils.isNullOrEmpty(countryShortcuts)) {
|
||||||
|
shortcuts.addAll(countryShortcuts.keySet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next look for the language-specific shortcut: en, fr, etc.
|
||||||
|
final Locale languageOnlyLocale =
|
||||||
|
LocaleUtils.constructLocaleFromString(inputLocale.getLanguage());
|
||||||
|
final Map<String, String> languageShortcuts = shortcutsPerLocale.get(languageOnlyLocale);
|
||||||
|
if (!CollectionUtils.isNullOrEmpty(languageShortcuts)) {
|
||||||
|
shortcuts.addAll(languageShortcuts.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all else fails, look for a global shortcut.
|
||||||
|
final Map<String, String> globalShortcuts = shortcutsPerLocale.get(ANY_LOCALE);
|
||||||
|
if (!CollectionUtils.isNullOrEmpty(globalShortcuts)) {
|
||||||
|
shortcuts.addAll(globalShortcuts.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
return shortcuts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the given word is a valid word in the given locale based on the dictionary.
|
||||||
* It tries hard to find a match: for example, casing is ignored and if the word is present in a
|
* 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
|
* 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.
|
* locale (e.g. en_US), it will be considered a match.
|
||||||
*
|
*
|
||||||
* @param word the word to match
|
* @param word the word to match
|
||||||
* @param locale the locale in which to match the word
|
* @param inputLocale the locale in which to match the word
|
||||||
* @return true iff the word has been matched for this locale in the UserDictionary.
|
* @return true iff the word has been matched for this locale in the dictionary.
|
||||||
*/
|
*/
|
||||||
public boolean isValidWord(@Nonnull final String word, @Nonnull final Locale locale) {
|
public boolean isValidWord(@Nonnull final String word, @Nonnull final Locale inputLocale) {
|
||||||
if (!isLoaded()) {
|
if (!isLoaded()) {
|
||||||
// This is a corner case in the event the initial load of UserDictionary has not
|
// This is a corner case in the event the initial load of the dictionary has not
|
||||||
// been loaded. In that case, we assume the word is not a valid word in
|
// completed. In that case, we assume the word is not a valid word in the dictionary.
|
||||||
// UserDictionary.
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
if (DEBUG) {
|
Log.d(mTag, "isValidWord() : Initial load not complete");
|
||||||
Log.d(mTag, "isValidWord invoked, but initial load not complete");
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -298,48 +402,44 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
// Atomically obtain the current copy of mDictWords;
|
// Atomically obtain the current copy of mDictWords;
|
||||||
final HashMap<String, ArrayList<Locale>> dictWords = mDictWords;
|
final HashMap<String, ArrayList<Locale>> dictWords = mDictWords;
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "isValidWord invoked for word [" + word +
|
Log.d(mTag, "isValidWord() : Word [" + word + "] in Locale [" + inputLocale + "]");
|
||||||
"] in locale " + locale);
|
|
||||||
}
|
}
|
||||||
// Lowercase the word using the given locale. Note, that dictionary
|
// Lowercase the word using the given locale. Note, that dictionary
|
||||||
// words are lowercased using their locale, and theoretically the
|
// words are lowercased using their locale, and theoretically the
|
||||||
// lowercasing between two matching locales may differ. For simplicity
|
// lowercasing between two matching locales may differ. For simplicity
|
||||||
// we ignore that possibility.
|
// we ignore that possibility.
|
||||||
final String lowercased = word.toLowerCase(locale);
|
final String lowercased = word.toLowerCase(inputLocale);
|
||||||
final ArrayList<Locale> dictLocales = dictWords.get(lowercased);
|
final ArrayList<Locale> dictLocales = dictWords.get(lowercased);
|
||||||
if (null == dictLocales) {
|
if (null == dictLocales) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "isValidWord=false, since there is no entry for " +
|
Log.d(mTag, "isValidWord() : No entry for lowercased word [" + lowercased + "]");
|
||||||
"lowercased word [" + lowercased + "]");
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "isValidWord found an entry for lowercased word [" + lowercased +
|
Log.d(mTag, "isValidWord() : Found entry for lowercased word [" + lowercased + "]");
|
||||||
"]; examining locales");
|
|
||||||
}
|
}
|
||||||
// Iterate over the locales this word is in.
|
// Iterate over the locales this word is in.
|
||||||
for (final Locale dictLocale : dictLocales) {
|
for (final Locale dictLocale : dictLocales) {
|
||||||
final int matchLevel = LocaleUtils.getMatchLevel(dictLocale.toString(),
|
final int matchLevel = LocaleUtils.getMatchLevel(dictLocale.toString(),
|
||||||
locale.toString());
|
inputLocale.toString());
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "matchLevel for dictLocale=" + dictLocale + ", locale=" +
|
Log.d(mTag, "isValidWord() : MatchLevel for DictLocale [" + dictLocale
|
||||||
locale + " is " + matchLevel);
|
+ "] and InputLocale [" + inputLocale + "] is " + matchLevel);
|
||||||
}
|
}
|
||||||
if (LocaleUtils.isMatch(matchLevel)) {
|
if (LocaleUtils.isMatch(matchLevel)) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "isValidWord=true, since matchLevel " + matchLevel +
|
Log.d(mTag, "isValidWord() : MatchLevel " + matchLevel + " IS a match");
|
||||||
" is a match");
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "matchLevel " + matchLevel + " is not a match");
|
Log.d(mTag, "isValidWord() : MatchLevel " + matchLevel + " is NOT a match");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "isValidWord=false, since none of the locales matched");
|
Log.d(mTag, "isValidWord() : False, since none of the locales matched");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -350,11 +450,11 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
*
|
*
|
||||||
* @param shortcut the shortcut to expand
|
* @param shortcut the shortcut to expand
|
||||||
* @param inputLocale the locale in which to expand the shortcut
|
* @param inputLocale the locale in which to expand the shortcut
|
||||||
* @return expanded shortcut iff the word is a shortcut in the UserDictionary.
|
* @return expanded shortcut iff the word is a shortcut in the dictionary.
|
||||||
*/
|
*/
|
||||||
@Nullable public String expandShortcut(
|
@Nullable public String expandShortcut(
|
||||||
@Nonnull final String shortcut, @Nonnull final Locale inputLocale) {
|
@Nonnull final String shortcut, @Nonnull final Locale inputLocale) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "expandShortcut() : Shortcut [" + shortcut + "] for [" + inputLocale + "]");
|
Log.d(mTag, "expandShortcut() : Shortcut [" + shortcut + "] for [" + inputLocale + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,6 +463,9 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
|
|
||||||
// Exit as early as possible. Most users don't use shortcuts.
|
// Exit as early as possible. Most users don't use shortcuts.
|
||||||
if (CollectionUtils.isNullOrEmpty(shortcutsPerLocale)) {
|
if (CollectionUtils.isNullOrEmpty(shortcutsPerLocale)) {
|
||||||
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
|
Log.d(mTag, "expandShortcut() : User has no shortcuts");
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,6 +474,10 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
final String expansionForCountry = expandShortcut(
|
final String expansionForCountry = expandShortcut(
|
||||||
shortcutsPerLocale, shortcut, inputLocale);
|
shortcutsPerLocale, shortcut, inputLocale);
|
||||||
if (!TextUtils.isEmpty(expansionForCountry)) {
|
if (!TextUtils.isEmpty(expansionForCountry)) {
|
||||||
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
|
Log.d(mTag, "expandShortcut() : Country expansion is ["
|
||||||
|
+ expansionForCountry + "]");
|
||||||
|
}
|
||||||
return expansionForCountry;
|
return expansionForCountry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,11 +488,19 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
final String expansionForLanguage = expandShortcut(
|
final String expansionForLanguage = expandShortcut(
|
||||||
shortcutsPerLocale, shortcut, languageOnlyLocale);
|
shortcutsPerLocale, shortcut, languageOnlyLocale);
|
||||||
if (!TextUtils.isEmpty(expansionForLanguage)) {
|
if (!TextUtils.isEmpty(expansionForLanguage)) {
|
||||||
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
|
Log.d(mTag, "expandShortcut() : Language expansion is ["
|
||||||
|
+ expansionForLanguage + "]");
|
||||||
|
}
|
||||||
return expansionForLanguage;
|
return expansionForLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all else fails, loof for a global shortcut.
|
// If all else fails, look for a global shortcut.
|
||||||
return expandShortcut(shortcutsPerLocale, shortcut, ANY_LOCALE);
|
final String expansionForGlobal = expandShortcut(shortcutsPerLocale, shortcut, ANY_LOCALE);
|
||||||
|
if (!TextUtils.isEmpty(expansionForGlobal) && DebugFlags.DEBUG_ENABLED) {
|
||||||
|
Log.d(mTag, "expandShortcut() : Global expansion is [" + expansionForGlobal + "]");
|
||||||
|
}
|
||||||
|
return expansionForGlobal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable private String expandShortcut(
|
@Nullable private String expandShortcut(
|
||||||
|
@ -399,60 +514,54 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
if (CollectionUtils.isNullOrEmpty(localeShortcuts)) {
|
if (CollectionUtils.isNullOrEmpty(localeShortcuts)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final String word = localeShortcuts.get(shortcut);
|
return 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.
|
* Loads the personal dictionary in the current thread.
|
||||||
*
|
*
|
||||||
* Only one reload can happen at a time. If already running, will exit quickly.
|
* Only one reload can happen at a time. If already running, will exit quickly.
|
||||||
*/
|
*/
|
||||||
private void loadUserDictionary() {
|
private void loadPersonalDictionary() {
|
||||||
// Bail out if already in the process of loading.
|
// Bail out if already in the process of loading.
|
||||||
if (!mIsLoading.compareAndSet(false, true)) {
|
if (!mIsLoading.compareAndSet(false, true)) {
|
||||||
Log.i(mTag, "loadUserDictionary() : Already Loading (exit)");
|
Log.i(mTag, "loadPersonalDictionary() : Already Loading (exit)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.i(mTag, "loadUserDictionary() : Start Loading");
|
Log.i(mTag, "loadPersonalDictionary() : Start Loading");
|
||||||
HashMap<String, ArrayList<Locale>> dictWords = new HashMap<>();
|
HashMap<String, ArrayList<Locale>> dictWords = new HashMap<>();
|
||||||
HashMap<Locale, HashMap<String, String>> shortcutsPerLocale = new HashMap<>();
|
HashMap<Locale, HashMap<String, String>> shortcutsPerLocale = new HashMap<>();
|
||||||
// Load the UserDictionary. Request that items be returned in the default sort order
|
// Load the dictionary. Items are returned in the default sort order (by frequency).
|
||||||
// for UserDictionary, which is by frequency.
|
|
||||||
Cursor cursor = mResolver.query(UserDictionary.Words.CONTENT_URI,
|
Cursor cursor = mResolver.query(UserDictionary.Words.CONTENT_URI,
|
||||||
null, null, null, UserDictionary.Words.DEFAULT_SORT_ORDER);
|
null, null, null, UserDictionary.Words.DEFAULT_SORT_ORDER);
|
||||||
if (null == cursor || cursor.getCount() < 1) {
|
if (null == cursor || cursor.getCount() < 1) {
|
||||||
Log.i(mTag, "loadUserDictionary() : Empty");
|
Log.i(mTag, "loadPersonalDictionary() : Empty");
|
||||||
} else {
|
} else {
|
||||||
// Iterate over the entries in the UserDictionary. Note, that iteration is in
|
// Iterate over the entries in the personal dictionary. Note, that iteration is in
|
||||||
// descending frequency by default.
|
// descending frequency by default.
|
||||||
while (dictWords.size() < MAX_NUM_ENTRIES && cursor.moveToNext()) {
|
while (dictWords.size() < MAX_NUM_ENTRIES && cursor.moveToNext()) {
|
||||||
// If there is no column for locale, skip this entry. An empty
|
// If there is no column for locale, skip this entry. An empty
|
||||||
// locale on the other hand will not be skipped.
|
// 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 (dictLocaleIndex < 0) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Encountered UserDictionary entry without LOCALE, skipping");
|
Log.d(mTag, "loadPersonalDictionary() : Entry without LOCALE, skipping");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If there is no column for word, skip this entry.
|
// 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 (dictWordIndex < 0) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Encountered UserDictionary entry without WORD, skipping");
|
Log.d(mTag, "loadPersonalDictionary() : Entry without WORD, skipping");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If the word is null, skip this entry.
|
// If the word is null, skip this entry.
|
||||||
final String rawDictWord = cursor.getString(dictWordIndex);
|
final String rawDictWord = cursor.getString(dictWordIndex);
|
||||||
if (null == rawDictWord) {
|
if (null == rawDictWord) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Encountered null word");
|
Log.d(mTag, "loadPersonalDictionary() : Null word");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -460,8 +569,8 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
// zz locale for an Alphabet (QWERTY) layout will not match any actual language.
|
// zz locale for an Alphabet (QWERTY) layout will not match any actual language.
|
||||||
String localeString = cursor.getString(dictLocaleIndex);
|
String localeString = cursor.getString(dictLocaleIndex);
|
||||||
if (null == localeString) {
|
if (null == localeString) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Encountered null locale for word [" +
|
Log.d(mTag, "loadPersonalDictionary() : Null locale for word [" +
|
||||||
rawDictWord + "], assuming all locales");
|
rawDictWord + "], assuming all locales");
|
||||||
}
|
}
|
||||||
// For purposes of LocaleUtils, an empty locale matches everything.
|
// For purposes of LocaleUtils, an empty locale matches everything.
|
||||||
|
@ -470,16 +579,16 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
final Locale dictLocale = LocaleUtils.constructLocaleFromString(localeString);
|
final Locale dictLocale = LocaleUtils.constructLocaleFromString(localeString);
|
||||||
// Lowercase the word before storing it.
|
// Lowercase the word before storing it.
|
||||||
final String dictWord = rawDictWord.toLowerCase(dictLocale);
|
final String dictWord = rawDictWord.toLowerCase(dictLocale);
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Incorporating UserDictionary word [" + dictWord +
|
Log.d(mTag, "loadPersonalDictionary() : Adding word [" + dictWord
|
||||||
"] for locale " + dictLocale);
|
+ "] for locale " + dictLocale);
|
||||||
}
|
}
|
||||||
// Check if there is an existing entry for this word.
|
// Check if there is an existing entry for this word.
|
||||||
ArrayList<Locale> dictLocales = dictWords.get(dictWord);
|
ArrayList<Locale> dictLocales = dictWords.get(dictWord);
|
||||||
if (null == dictLocales) {
|
if (null == dictLocales) {
|
||||||
// If there is no entry for this word, create one.
|
// If there is no entry for this word, create one.
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Word [" + dictWord +
|
Log.d(mTag, "loadPersonalDictionary() : Word [" + dictWord +
|
||||||
"] not seen for other locales, creating new entry");
|
"] not seen for other locales, creating new entry");
|
||||||
}
|
}
|
||||||
dictLocales = new ArrayList<>();
|
dictLocales = new ArrayList<>();
|
||||||
|
@ -491,16 +600,16 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
// If there is no column for a shortcut, we're done.
|
// If there is no column for a shortcut, we're done.
|
||||||
final int shortcutIndex = cursor.getColumnIndex(UserDictionary.Words.SHORTCUT);
|
final int shortcutIndex = cursor.getColumnIndex(UserDictionary.Words.SHORTCUT);
|
||||||
if (shortcutIndex < 0) {
|
if (shortcutIndex < 0) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Encountered UserDictionary entry without SHORTCUT, done");
|
Log.d(mTag, "loadPersonalDictionary() : Entry without SHORTCUT, done");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If the shortcut is null, we're done.
|
// If the shortcut is null, we're done.
|
||||||
final String shortcut = cursor.getString(shortcutIndex);
|
final String shortcut = cursor.getString(shortcutIndex);
|
||||||
if (shortcut == null) {
|
if (shortcut == null) {
|
||||||
if (DEBUG) {
|
if (DebugFlags.DEBUG_ENABLED) {
|
||||||
Log.d(mTag, "Encountered null shortcut");
|
Log.d(mTag, "loadPersonalDictionary() : Null shortcut");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -529,10 +638,12 @@ public class UserDictionaryLookup implements Closeable {
|
||||||
mDictWords = dictWords;
|
mDictWords = dictWords;
|
||||||
mShortcutsPerLocale = shortcutsPerLocale;
|
mShortcutsPerLocale = shortcutsPerLocale;
|
||||||
|
|
||||||
// Allow other calls to loadUserDictionary to execute now.
|
// Allow other calls to loadPersonalDictionary to execute now.
|
||||||
mIsLoading.set(false);
|
mIsLoading.set(false);
|
||||||
|
|
||||||
Log.i(mTag, "loadUserDictionary() : Loaded " + mDictWords.size()
|
Log.i(mTag, "loadPersonalDictionary() : Loaded " + mDictWords.size()
|
||||||
+ " words and " + numShortcuts + " shortcuts");
|
+ " words and " + numShortcuts + " shortcuts");
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,12 @@
|
||||||
|
|
||||||
package com.android.inputmethod.latin;
|
package com.android.inputmethod.latin;
|
||||||
|
|
||||||
import static com.android.inputmethod.latin.UserDictionaryLookup.ANY_LOCALE;
|
import static com.android.inputmethod.latin.PersonalDictionaryLookup.ANY_LOCALE;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
@ -27,20 +32,22 @@ import android.test.AndroidTestCase;
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.inputmethod.latin.PersonalDictionaryLookup.PersonalDictionaryListener;
|
||||||
import com.android.inputmethod.latin.utils.ExecutorUtils;
|
import com.android.inputmethod.latin.utils.ExecutorUtils;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link com.android.inputmethod.latin.UserDictionaryLookup}.
|
* Unit tests for {@link PersonalDictionaryLookup}.
|
||||||
*
|
*
|
||||||
* Note, this test doesn't mock out the ContentResolver, in order to make sure UserDictionaryLookup
|
* Note, this test doesn't mock out the ContentResolver, in order to make sure
|
||||||
* works in a real setting.
|
* {@link PersonalDictionaryLookup} works in a real setting.
|
||||||
*/
|
*/
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class UserDictionaryLookupTest extends AndroidTestCase {
|
public class PersonalDictionaryLookupTest extends AndroidTestCase {
|
||||||
private static final String TAG = UserDictionaryLookupTest.class.getSimpleName();
|
private static final String TAG = PersonalDictionaryLookupTest.class.getSimpleName();
|
||||||
|
|
||||||
private ContentResolver mContentResolver;
|
private ContentResolver mContentResolver;
|
||||||
private HashSet<Uri> mAddedBackup;
|
private HashSet<Uri> mAddedBackup;
|
||||||
|
@ -64,7 +71,7 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the given word to UserDictionary.
|
* Adds the given word to the personal dictionary.
|
||||||
*
|
*
|
||||||
* @param word the word to add
|
* @param word the word to add
|
||||||
* @param locale the locale of the word to add
|
* @param locale the locale of the word to add
|
||||||
|
@ -94,25 +101,43 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
private void deleteWord(Uri uri) {
|
private void deleteWord(Uri uri) {
|
||||||
// Remove the word from the backup so that it's not cleared again later.
|
// Remove the word from the backup so that it's not cleared again later.
|
||||||
mAddedBackup.remove(uri);
|
mAddedBackup.remove(uri);
|
||||||
// Remove the word from UserDictionary.
|
// Remove the word from the personal dictionary.
|
||||||
mContentResolver.delete(uri, null, null);
|
mContentResolver.delete(uri, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserDictionaryLookup setUpShortcut(final Locale locale) {
|
private PersonalDictionaryLookup setUpWord(final Locale locale) {
|
||||||
// Insert "shortcut" => "Expansion" in the UserDictionary for the given locale.
|
// Insert "foo" in the personal dictionary for the given locale.
|
||||||
|
addWord("foo", locale, 17, null);
|
||||||
|
|
||||||
|
// Create the PersonalDictionaryLookup and wait until it's loaded.
|
||||||
|
PersonalDictionaryLookup lookup =
|
||||||
|
new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
||||||
|
lookup.open();
|
||||||
|
return lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PersonalDictionaryLookup setUpShortcut(final Locale locale) {
|
||||||
|
// Insert "shortcut" => "Expansion" in the personal dictionary for the given locale.
|
||||||
addWord("Expansion", locale, 17, "shortcut");
|
addWord("Expansion", locale, 17, "shortcut");
|
||||||
|
|
||||||
// Create the UserDictionaryLookup and wait until it's loaded.
|
// Create the PersonalDictionaryLookup and wait until it's loaded.
|
||||||
UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
PersonalDictionaryLookup lookup =
|
||||||
|
new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
||||||
lookup.open();
|
lookup.open();
|
||||||
while (!lookup.isLoaded()) {
|
|
||||||
}
|
|
||||||
return lookup;
|
return lookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void verifyWordExists(final Set<String> set, final String word) {
|
||||||
|
assertTrue(set.contains(word));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyWordDoesNotExist(final Set<String> set, final String word) {
|
||||||
|
assertFalse(set.contains(word));
|
||||||
|
}
|
||||||
|
|
||||||
public void testShortcutKeyMatching() {
|
public void testShortcutKeyMatching() {
|
||||||
Log.d(TAG, "testShortcutKeyMatching");
|
Log.d(TAG, "testShortcutKeyMatching");
|
||||||
UserDictionaryLookup lookup = setUpShortcut(Locale.US);
|
PersonalDictionaryLookup lookup = setUpShortcut(Locale.US);
|
||||||
|
|
||||||
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
|
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
|
||||||
assertNull(lookup.expandShortcut("Shortcut", Locale.US));
|
assertNull(lookup.expandShortcut("Shortcut", Locale.US));
|
||||||
|
@ -125,7 +150,13 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
|
|
||||||
public void testShortcutMatchesInputCountry() {
|
public void testShortcutMatchesInputCountry() {
|
||||||
Log.d(TAG, "testShortcutMatchesInputCountry");
|
Log.d(TAG, "testShortcutMatchesInputCountry");
|
||||||
UserDictionaryLookup lookup = setUpShortcut(Locale.US);
|
PersonalDictionaryLookup lookup = setUpShortcut(Locale.US);
|
||||||
|
|
||||||
|
verifyWordExists(lookup.getShortcutsForLocale(Locale.US), "shortcut");
|
||||||
|
assertTrue(lookup.getShortcutsForLocale(Locale.UK).isEmpty());
|
||||||
|
assertTrue(lookup.getShortcutsForLocale(Locale.ENGLISH).isEmpty());
|
||||||
|
assertTrue(lookup.getShortcutsForLocale(Locale.FRENCH).isEmpty());
|
||||||
|
assertTrue(lookup.getShortcutsForLocale(ANY_LOCALE).isEmpty());
|
||||||
|
|
||||||
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
|
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
|
||||||
assertNull(lookup.expandShortcut("shortcut", Locale.UK));
|
assertNull(lookup.expandShortcut("shortcut", Locale.UK));
|
||||||
|
@ -138,7 +169,13 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
|
|
||||||
public void testShortcutMatchesInputLanguage() {
|
public void testShortcutMatchesInputLanguage() {
|
||||||
Log.d(TAG, "testShortcutMatchesInputLanguage");
|
Log.d(TAG, "testShortcutMatchesInputLanguage");
|
||||||
UserDictionaryLookup lookup = setUpShortcut(Locale.ENGLISH);
|
PersonalDictionaryLookup lookup = setUpShortcut(Locale.ENGLISH);
|
||||||
|
|
||||||
|
verifyWordExists(lookup.getShortcutsForLocale(Locale.US), "shortcut");
|
||||||
|
verifyWordExists(lookup.getShortcutsForLocale(Locale.UK), "shortcut");
|
||||||
|
verifyWordExists(lookup.getShortcutsForLocale(Locale.ENGLISH), "shortcut");
|
||||||
|
assertTrue(lookup.getShortcutsForLocale(Locale.FRENCH).isEmpty());
|
||||||
|
assertTrue(lookup.getShortcutsForLocale(ANY_LOCALE).isEmpty());
|
||||||
|
|
||||||
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
|
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
|
||||||
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK));
|
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK));
|
||||||
|
@ -150,7 +187,13 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testShortcutMatchesAnyLocale() {
|
public void testShortcutMatchesAnyLocale() {
|
||||||
UserDictionaryLookup lookup = setUpShortcut(UserDictionaryLookup.ANY_LOCALE);
|
PersonalDictionaryLookup lookup = setUpShortcut(PersonalDictionaryLookup.ANY_LOCALE);
|
||||||
|
|
||||||
|
verifyWordExists(lookup.getShortcutsForLocale(Locale.US), "shortcut");
|
||||||
|
verifyWordExists(lookup.getShortcutsForLocale(Locale.UK), "shortcut");
|
||||||
|
verifyWordExists(lookup.getShortcutsForLocale(Locale.ENGLISH), "shortcut");
|
||||||
|
verifyWordExists(lookup.getShortcutsForLocale(Locale.FRENCH), "shortcut");
|
||||||
|
verifyWordExists(lookup.getShortcutsForLocale(ANY_LOCALE), "shortcut");
|
||||||
|
|
||||||
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
|
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.US));
|
||||||
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK));
|
assertEquals("Expansion", lookup.expandShortcut("shortcut", Locale.UK));
|
||||||
|
@ -163,15 +206,13 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
|
|
||||||
public void testExactLocaleMatch() {
|
public void testExactLocaleMatch() {
|
||||||
Log.d(TAG, "testExactLocaleMatch");
|
Log.d(TAG, "testExactLocaleMatch");
|
||||||
|
PersonalDictionaryLookup lookup = setUpWord(Locale.US);
|
||||||
|
|
||||||
// Insert "Foo" as capitalized in the UserDictionary under en_US locale.
|
verifyWordExists(lookup.getWordsForLocale(Locale.US), "foo");
|
||||||
addWord("Foo", Locale.US, 17, null);
|
verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.UK), "foo");
|
||||||
|
verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.ENGLISH), "foo");
|
||||||
// Create the UserDictionaryLookup and wait until it's loaded.
|
verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.FRENCH), "foo");
|
||||||
UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
verifyWordDoesNotExist(lookup.getWordsForLocale(ANY_LOCALE), "foo");
|
||||||
lookup.open();
|
|
||||||
while (!lookup.isLoaded()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any capitalization variation should match.
|
// Any capitalization variation should match.
|
||||||
assertTrue(lookup.isValidWord("foo", Locale.US));
|
assertTrue(lookup.isValidWord("foo", Locale.US));
|
||||||
|
@ -192,15 +233,13 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
|
|
||||||
public void testSubLocaleMatch() {
|
public void testSubLocaleMatch() {
|
||||||
Log.d(TAG, "testSubLocaleMatch");
|
Log.d(TAG, "testSubLocaleMatch");
|
||||||
|
PersonalDictionaryLookup lookup = setUpWord(Locale.ENGLISH);
|
||||||
|
|
||||||
// Insert "Foo" as capitalized in the UserDictionary under the en locale.
|
verifyWordExists(lookup.getWordsForLocale(Locale.US), "foo");
|
||||||
addWord("Foo", Locale.ENGLISH, 17, null);
|
verifyWordExists(lookup.getWordsForLocale(Locale.UK), "foo");
|
||||||
|
verifyWordExists(lookup.getWordsForLocale(Locale.ENGLISH), "foo");
|
||||||
// Create the UserDictionaryLookup and wait until it's loaded.
|
verifyWordDoesNotExist(lookup.getWordsForLocale(Locale.FRENCH), "foo");
|
||||||
UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
verifyWordDoesNotExist(lookup.getWordsForLocale(ANY_LOCALE), "foo");
|
||||||
lookup.open();
|
|
||||||
while (!lookup.isLoaded()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any capitalization variation should match for both en and en_US.
|
// Any capitalization variation should match for both en and en_US.
|
||||||
assertTrue(lookup.isValidWord("foo", Locale.ENGLISH));
|
assertTrue(lookup.isValidWord("foo", Locale.ENGLISH));
|
||||||
|
@ -217,15 +256,13 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
|
|
||||||
public void testAllLocalesMatch() {
|
public void testAllLocalesMatch() {
|
||||||
Log.d(TAG, "testAllLocalesMatch");
|
Log.d(TAG, "testAllLocalesMatch");
|
||||||
|
PersonalDictionaryLookup lookup = setUpWord(null);
|
||||||
|
|
||||||
// Insert "Foo" as capitalized in the UserDictionary under the all locales.
|
verifyWordExists(lookup.getWordsForLocale(Locale.US), "foo");
|
||||||
addWord("Foo", null, 17, null);
|
verifyWordExists(lookup.getWordsForLocale(Locale.UK), "foo");
|
||||||
|
verifyWordExists(lookup.getWordsForLocale(Locale.ENGLISH), "foo");
|
||||||
// Create the UserDictionaryLookup and wait until it's loaded.
|
verifyWordExists(lookup.getWordsForLocale(Locale.FRENCH), "foo");
|
||||||
UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
verifyWordExists(lookup.getWordsForLocale(ANY_LOCALE), "foo");
|
||||||
lookup.open();
|
|
||||||
while (!lookup.isLoaded()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any capitalization variation should match for fr, en and en_US.
|
// Any capitalization variation should match for fr, en and en_US.
|
||||||
assertTrue(lookup.isValidWord("foo", ANY_LOCALE));
|
assertTrue(lookup.isValidWord("foo", ANY_LOCALE));
|
||||||
|
@ -245,17 +282,15 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
public void testMultipleLocalesMatch() {
|
public void testMultipleLocalesMatch() {
|
||||||
Log.d(TAG, "testMultipleLocalesMatch");
|
Log.d(TAG, "testMultipleLocalesMatch");
|
||||||
|
|
||||||
// Insert "Foo" as capitalized in the UserDictionary under the en_US and en_CA and fr
|
// Insert "Foo" as capitalized in the personal dictionary under the en_US and en_CA and fr
|
||||||
// locales.
|
// locales.
|
||||||
addWord("Foo", Locale.US, 17, null);
|
addWord("Foo", Locale.US, 17, null);
|
||||||
addWord("foO", Locale.CANADA, 17, null);
|
addWord("foO", Locale.CANADA, 17, null);
|
||||||
addWord("fOo", Locale.FRENCH, 17, null);
|
addWord("fOo", Locale.FRENCH, 17, null);
|
||||||
|
|
||||||
// Create the UserDictionaryLookup and wait until it's loaded.
|
// Create the PersonalDictionaryLookup and wait until it's loaded.
|
||||||
UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
PersonalDictionaryLookup lookup = new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
||||||
lookup.open();
|
lookup.open();
|
||||||
while (!lookup.isLoaded()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both en_CA and en_US match.
|
// Both en_CA and en_US match.
|
||||||
assertTrue(lookup.isValidWord("foo", Locale.CANADA));
|
assertTrue(lookup.isValidWord("foo", Locale.CANADA));
|
||||||
|
@ -269,17 +304,40 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
lookup.close();
|
lookup.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testManageListeners() {
|
||||||
|
Log.d(TAG, "testManageListeners");
|
||||||
|
|
||||||
|
PersonalDictionaryLookup lookup =
|
||||||
|
new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
||||||
|
|
||||||
|
PersonalDictionaryListener listener = mock(PersonalDictionaryListener.class);
|
||||||
|
// Add the same listener a bunch of times. It doesn't make a difference.
|
||||||
|
lookup.addListener(listener);
|
||||||
|
lookup.addListener(listener);
|
||||||
|
lookup.addListener(listener);
|
||||||
|
lookup.notifyListeners();
|
||||||
|
|
||||||
|
verify(listener, times(1)).onUpdate();
|
||||||
|
|
||||||
|
// Remove the same listener a bunch of times. It doesn't make a difference.
|
||||||
|
lookup.removeListener(listener);
|
||||||
|
lookup.removeListener(listener);
|
||||||
|
lookup.removeListener(listener);
|
||||||
|
lookup.notifyListeners();
|
||||||
|
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public void testReload() {
|
public void testReload() {
|
||||||
Log.d(TAG, "testReload");
|
Log.d(TAG, "testReload");
|
||||||
|
|
||||||
// Insert "foo".
|
// Insert "foo".
|
||||||
Uri uri = addWord("foo", Locale.US, 17, null);
|
Uri uri = addWord("foo", Locale.US, 17, null);
|
||||||
|
|
||||||
// Create the UserDictionaryLookup and wait until it's loaded.
|
// Create the PersonalDictionaryLookup and wait until it's loaded.
|
||||||
UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
PersonalDictionaryLookup lookup =
|
||||||
|
new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
||||||
lookup.open();
|
lookup.open();
|
||||||
while (!lookup.isLoaded()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// "foo" should match.
|
// "foo" should match.
|
||||||
assertTrue(lookup.isValidWord("foo", Locale.US));
|
assertTrue(lookup.isValidWord("foo", Locale.US));
|
||||||
|
@ -292,9 +350,9 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
addWord("bar", Locale.US, 18, null);
|
addWord("bar", Locale.US, 18, null);
|
||||||
|
|
||||||
// Wait a little bit before expecting a change. The time we wait should be greater than
|
// Wait a little bit before expecting a change. The time we wait should be greater than
|
||||||
// UserDictionaryLookup.RELOAD_DELAY_MS.
|
// PersonalDictionaryLookup.RELOAD_DELAY_MS.
|
||||||
try {
|
try {
|
||||||
Thread.sleep(UserDictionaryLookup.RELOAD_DELAY_MS + 1000);
|
Thread.sleep(PersonalDictionaryLookup.RELOAD_DELAY_MS + 1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,11 +374,10 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
Uri uri = addWord("foo", Locale.GERMANY, 17, "f");
|
Uri uri = addWord("foo", Locale.GERMANY, 17, "f");
|
||||||
addWord("bar", Locale.GERMANY, 17, null);
|
addWord("bar", Locale.GERMANY, 17, null);
|
||||||
|
|
||||||
// Create the UserDictionaryLookup and wait until it's loaded.
|
// Create the PersonalDictionaryLookup and wait until it's loaded.
|
||||||
UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
PersonalDictionaryLookup lookup =
|
||||||
|
new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
||||||
lookup.open();
|
lookup.open();
|
||||||
while (!lookup.isLoaded()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// "foo" should match.
|
// "foo" should match.
|
||||||
assertTrue(lookup.isValidWord("foo", Locale.GERMANY));
|
assertTrue(lookup.isValidWord("foo", Locale.GERMANY));
|
||||||
|
@ -335,9 +392,9 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
deleteWord(uri);
|
deleteWord(uri);
|
||||||
|
|
||||||
// Wait a little bit before expecting a change. The time we wait should be greater than
|
// Wait a little bit before expecting a change. The time we wait should be greater than
|
||||||
// UserDictionaryLookup.RELOAD_DELAY_MS.
|
// PersonalDictionaryLookup.RELOAD_DELAY_MS.
|
||||||
try {
|
try {
|
||||||
Thread.sleep(UserDictionaryLookup.RELOAD_DELAY_MS + 1000);
|
Thread.sleep(PersonalDictionaryLookup.RELOAD_DELAY_MS + 1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,11 +418,10 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
// Insert "foo".
|
// Insert "foo".
|
||||||
Uri uri = addWord("foo", Locale.US, 17, null);
|
Uri uri = addWord("foo", Locale.US, 17, null);
|
||||||
|
|
||||||
// Create the UserDictionaryLookup and wait until it's loaded.
|
// Create the PersonalDictionaryLookup and wait until it's loaded.
|
||||||
UserDictionaryLookup lookup = new UserDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
PersonalDictionaryLookup lookup =
|
||||||
|
new PersonalDictionaryLookup(mContext, ExecutorUtils.SPELLING);
|
||||||
lookup.open();
|
lookup.open();
|
||||||
while (!lookup.isLoaded()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// "foo" should match.
|
// "foo" should match.
|
||||||
assertTrue(lookup.isValidWord("foo", Locale.US));
|
assertTrue(lookup.isValidWord("foo", Locale.US));
|
||||||
|
@ -381,9 +437,9 @@ public class UserDictionaryLookupTest extends AndroidTestCase {
|
||||||
addWord("bar", Locale.US, 18, null);
|
addWord("bar", Locale.US, 18, null);
|
||||||
|
|
||||||
// Wait a little bit before expecting a change. The time we wait should be greater than
|
// Wait a little bit before expecting a change. The time we wait should be greater than
|
||||||
// UserDictionaryLookup.RELOAD_DELAY_MS.
|
// PersonalDictionaryLookup.RELOAD_DELAY_MS.
|
||||||
try {
|
try {
|
||||||
Thread.sleep(UserDictionaryLookup.RELOAD_DELAY_MS + 1000);
|
Thread.sleep(PersonalDictionaryLookup.RELOAD_DELAY_MS + 1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package com.android.inputmethod.latin.settings;
|
package com.android.inputmethod.latin.settings;
|
||||||
|
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
|
|
@ -82,12 +82,10 @@ public class CollectionUtilsTests extends AndroidTestCase {
|
||||||
* results for a few cases.
|
* results for a few cases.
|
||||||
*/
|
*/
|
||||||
public void testIsNullOrEmpty() {
|
public void testIsNullOrEmpty() {
|
||||||
assertTrue(CollectionUtils.isNullOrEmpty((List) null));
|
assertTrue(CollectionUtils.isNullOrEmpty((List<String>) null));
|
||||||
assertTrue(CollectionUtils.isNullOrEmpty((Map) null));
|
assertTrue(CollectionUtils.isNullOrEmpty((Map<String, String>) null));
|
||||||
assertTrue(CollectionUtils.isNullOrEmpty(new ArrayList()));
|
assertTrue(CollectionUtils.isNullOrEmpty(new ArrayList<String>()));
|
||||||
assertTrue(CollectionUtils.isNullOrEmpty(new HashMap()));
|
assertTrue(CollectionUtils.isNullOrEmpty(new HashMap<String, String>()));
|
||||||
assertTrue(CollectionUtils.isNullOrEmpty(Collections.EMPTY_LIST));
|
|
||||||
assertTrue(CollectionUtils.isNullOrEmpty(Collections.EMPTY_MAP));
|
|
||||||
assertFalse(CollectionUtils.isNullOrEmpty(Collections.singletonList("Not empty")));
|
assertFalse(CollectionUtils.isNullOrEmpty(Collections.singletonList("Not empty")));
|
||||||
assertFalse(CollectionUtils.isNullOrEmpty(Collections.singletonMap("Not", "empty")));
|
assertFalse(CollectionUtils.isNullOrEmpty(Collections.singletonMap("Not", "empty")));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue