aboutsummaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsContentObserver.java54
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsManager.java1
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitator.java12
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java31
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java97
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java13
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java2
-rw-r--r--java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java45
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java55
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java6
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java22
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java88
12 files changed, 235 insertions, 191 deletions
diff --git a/java/src/com/android/inputmethod/latin/ContactsContentObserver.java b/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
index 019d17d56..e45681bd7 100644
--- a/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
+++ b/java/src/com/android/inputmethod/latin/ContactsContentObserver.java
@@ -23,26 +23,26 @@ import android.os.SystemClock;
import android.provider.ContactsContract.Contacts;
import android.util.Log;
-import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.ContactsManager.ContactsChangedListener;
import com.android.inputmethod.latin.utils.ExecutorUtils;
import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
+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}.
*/
-// TODO:add test
-public class ContactsContentObserver {
+public class ContactsContentObserver implements Runnable {
private static final String TAG = ContactsContentObserver.class.getSimpleName();
private static final boolean DEBUG = false;
-
- private ContentObserver mObserver;
+ private static AtomicBoolean sRunning = new AtomicBoolean(false);
private final Context mContext;
private final ContactsManager mManager;
+ private ContentObserver mContentObserver;
+ private ContactsChangedListener mContactsChangedListener;
+
public ContactsContentObserver(final ContactsManager manager, final Context context) {
mManager = manager;
mContext = context;
@@ -52,32 +52,36 @@ public class ContactsContentObserver {
if (DEBUG) {
Log.d(TAG, "Registered Contacts Content Observer");
}
- mObserver = new ContentObserver(null /* handler */) {
+ mContactsChangedListener = listener;
+ mContentObserver = new ContentObserver(null /* handler */) {
@Override
public void onChange(boolean self) {
- getBgExecutor().execute(new Runnable() {
- @Override
- public void run() {
- if (haveContentsChanged()) {
- if (DEBUG) {
- Log.d(TAG, "Contacts have changed; notifying listeners");
- }
- listener.onContactsChange();
- }
- }
- });
+ ExecutorUtils.getExecutorForDynamicLanguageModelUpdate()
+ .execute(ContactsContentObserver.this);
}
};
final ContentResolver contentResolver = mContext.getContentResolver();
- contentResolver.registerContentObserver(Contacts.CONTENT_URI, true, mObserver);
+ contentResolver.registerContentObserver(Contacts.CONTENT_URI, true, mContentObserver);
}
- @UsedForTesting
- private ExecutorService getBgExecutor() {
- return ExecutorUtils.getExecutor("Check Contacts");
+ @Override
+ public void run() {
+ if (!sRunning.compareAndSet(false /* expect */, true /* update */)) {
+ if (DEBUG) {
+ Log.d(TAG, "run() : Already running. Don't waste time checking again.");
+ }
+ return;
+ }
+ if (haveContentsChanged()) {
+ if (DEBUG) {
+ Log.d(TAG, "run() : Contacts have changed. Notifying listeners.");
+ }
+ mContactsChangedListener.onContactsChange();
+ }
+ sRunning.set(false);
}
- private boolean haveContentsChanged() {
+ boolean haveContentsChanged() {
final long startTime = SystemClock.uptimeMillis();
final int contactCount = mManager.getContactCount();
if (contactCount > ContactsDictionaryConstants.MAX_CONTACT_COUNT) {
@@ -105,6 +109,6 @@ public class ContactsContentObserver {
}
public void unregister() {
- mContext.getContentResolver().unregisterContentObserver(mObserver);
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
}
}
diff --git a/java/src/com/android/inputmethod/latin/ContactsManager.java b/java/src/com/android/inputmethod/latin/ContactsManager.java
index dc5abd955..1fadc6f6f 100644
--- a/java/src/com/android/inputmethod/latin/ContactsManager.java
+++ b/java/src/com/android/inputmethod/latin/ContactsManager.java
@@ -34,7 +34,6 @@ import java.util.concurrent.atomic.AtomicInteger;
* The manager provides an API for listening to meaning full updates by keeping a
* measure of the current state of the content provider.
*/
-// TODO:Add test
public class ContactsManager {
private static final String TAG = ContactsManager.class.getSimpleName();
private static final boolean DEBUG = false;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index c22dc287c..addc8f209 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -150,7 +150,9 @@ public interface DictionaryFacilitator {
@Nonnull final NgramContext ngramContext, final int timeStampInSeconds,
final boolean blockPotentiallyOffensive);
- void removeWordFromPersonalizedDicts(final String word);
+ void unlearnFromUserHistory(final String word,
+ @Nonnull final NgramContext ngramContext, final int timeStampInSeconds,
+ final int eventType);
// TODO: Revise the way to fusion suggestion results.
SuggestionResults getSuggestionResults(final WordComposer composer,
@@ -171,12 +173,4 @@ public interface DictionaryFacilitator {
void dumpDictionaryForDebug(final String dictName);
ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts();
-
- void addOrIncrementTerm(String fileName,
- String finalWordToBeAdded,
- NgramContext ngramContext,
- int increment,
- int timeStampInSeconds);
-
- void clearLanguageModel(String filePath);
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
index 3d76751ce..1e0885420 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+7 * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -443,7 +443,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
final Locale[] locales, final DictionaryInitializationListener listener) {
final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
- ExecutorUtils.getExecutor("InitializeBinaryDictionary").execute(new Runnable() {
+ ExecutorUtils.getExecutorForStaticLanguageModelUpdate().execute(new Runnable() {
@Override
public void run() {
doReloadUninitializedMainDictionaries(
@@ -654,8 +654,14 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
}
- public void removeWordFromPersonalizedDicts(final String word) {
- removeWord(Dictionary.TYPE_USER_HISTORY, word);
+ @Override
+ public void unlearnFromUserHistory(final String word,
+ @Nonnull final NgramContext ngramContext, final int timeStampInSeconds,
+ final int eventType) {
+ // TODO: Decide whether or not to remove the word on EVENT_BACKSPACE.
+ if (eventType != Constants.EVENT_BACKSPACE) {
+ removeWord(Dictionary.TYPE_USER_HISTORY, word);
+ }
}
// TODO: Revise the way to fusion suggestion results.
@@ -766,10 +772,12 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
}
+ @Override
public void clearUserHistoryDictionary() {
clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
}
+ @Override
public void dumpDictionaryForDebug(final String dictName) {
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
@@ -783,6 +791,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
}
+ @Override
public ArrayList<Pair<String, DictionaryStats>> getStatsOfEnabledSubDicts() {
final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>();
final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
@@ -795,18 +804,4 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
}
return statsOfEnabledSubDicts;
}
-
- @Override
- public void addOrIncrementTerm(String fileName,
- String word,
- NgramContext ngramContext,
- int increment,
- int timeStampInSeconds) {
- // Do nothing.
- }
-
- @Override
- public void clearLanguageModel(String filePath) {
- // Do nothing.
- }
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
index 85ecf93f3..b813af4c2 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
@@ -16,15 +16,11 @@
package com.android.inputmethod.latin;
-import java.util.HashSet;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
-import com.android.inputmethod.annotations.UsedForTesting;
-
import android.content.Context;
import android.util.Log;
-import android.util.LruCache;
/**
* Cache for dictionary facilitators of multiple locales.
@@ -32,54 +28,20 @@ import android.util.LruCache;
*/
public class DictionaryFacilitatorLruCache {
private static final String TAG = "DictionaryFacilitatorLruCache";
- private static final int MAX_DICTIONARY_FACILITATOR_COUNT = 3;
private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
- /**
- * Class extends LruCache. This class tracks cached locales and closes evicted dictionaries by
- * overriding entryRemoved.
- */
- private static class DictionaryFacilitatorLruCacheInner extends
- LruCache<Locale, DictionaryFacilitator> {
- private final HashSet<Locale> mCachedLocales;
- public DictionaryFacilitatorLruCacheInner(final HashSet<Locale> cachedLocales,
- final int maxSize) {
- super(maxSize);
- mCachedLocales = cachedLocales;
- }
-
- @Override
- protected void entryRemoved(boolean evicted, Locale key,
- DictionaryFacilitator oldValue, DictionaryFacilitator newValue) {
- if (oldValue != null && oldValue != newValue) {
- oldValue.closeDictionaries();
- }
- if (key != null && newValue == null) {
- // Remove locale from the cache when the dictionary facilitator for the locale is
- // evicted and new facilitator is not set for the locale.
- mCachedLocales.remove(key);
- if (size() >= maxSize()) {
- Log.w(TAG, "DictionaryFacilitator for " + key.toString()
- + " has been evicted due to cache size limit."
- + " size: " + size() + ", maxSize: " + maxSize());
- }
- }
- }
- }
-
private final Context mContext;
- private final HashSet<Locale> mCachedLocales = new HashSet<>();
private final String mDictionaryNamePrefix;
- private final DictionaryFacilitatorLruCacheInner mLruCache;
private final Object mLock = new Object();
- private boolean mUseContactsDictionary = false;
+ private final DictionaryFacilitator mDictionaryFacilitator;
+ private boolean mUseContactsDictionary;
+ private Locale mLocale;
public DictionaryFacilitatorLruCache(final Context context, final String dictionaryNamePrefix) {
mContext = context;
- mLruCache = new DictionaryFacilitatorLruCacheInner(
- mCachedLocales, MAX_DICTIONARY_FACILITATOR_COUNT);
mDictionaryNamePrefix = dictionaryNamePrefix;
+ mDictionaryFacilitator = DictionaryFacilitatorProvider.getDictionaryFacilitator();
}
private static void waitForLoadingMainDictionary(
@@ -101,59 +63,40 @@ public class DictionaryFacilitatorLruCache {
}
}
- private void resetDictionariesForLocaleLocked(final DictionaryFacilitator dictionaryFacilitator,
- final Locale locale) {
+ private void resetDictionariesForLocaleLocked() {
// Note: Given that personalized dictionaries are not used here; we can pass null account.
- dictionaryFacilitator.resetDictionaries(mContext, new Locale[]{locale},
+ mDictionaryFacilitator.resetDictionaries(mContext, new Locale[]{mLocale},
mUseContactsDictionary, false /* usePersonalizedDicts */,
false /* forceReloadMainDictionary */, null /* account */,
mDictionaryNamePrefix, null /* listener */);
}
- public void setUseContactsDictionary(final boolean useContectsDictionary) {
- if (mUseContactsDictionary == useContectsDictionary) {
- // The value has not been changed.
- return;
- }
+ public void setUseContactsDictionary(final boolean useContactsDictionary) {
synchronized (mLock) {
- mUseContactsDictionary = useContectsDictionary;
- for (final Locale locale : mCachedLocales) {
- final DictionaryFacilitator dictionaryFacilitator = mLruCache.get(locale);
- resetDictionariesForLocaleLocked(dictionaryFacilitator, locale);
- waitForLoadingMainDictionary(dictionaryFacilitator);
+ if (mUseContactsDictionary == useContactsDictionary) {
+ // The value has not been changed.
+ return;
}
+ mUseContactsDictionary = useContactsDictionary;
+ resetDictionariesForLocaleLocked();
+ waitForLoadingMainDictionary(mDictionaryFacilitator);
}
}
public DictionaryFacilitator get(final Locale locale) {
- DictionaryFacilitator dictionaryFacilitator = mLruCache.get(locale);
- if (dictionaryFacilitator != null) {
- // dictionary facilitator for the locale is in the cache.
- return dictionaryFacilitator;
- }
synchronized (mLock) {
- dictionaryFacilitator = mLruCache.get(locale);
- if (dictionaryFacilitator != null) {
- return dictionaryFacilitator;
+ if (!mDictionaryFacilitator.isForLocales(new Locale[]{locale})) {
+ mLocale = locale;
+ resetDictionariesForLocaleLocked();
}
- dictionaryFacilitator = DictionaryFacilitatorProvider.newDictionaryFacilitator();
- resetDictionariesForLocaleLocked(dictionaryFacilitator, locale);
- waitForLoadingMainDictionary(dictionaryFacilitator);
- mLruCache.put(locale, dictionaryFacilitator);
- mCachedLocales.add(locale);
- return dictionaryFacilitator;
+ waitForLoadingMainDictionary(mDictionaryFacilitator);
+ return mDictionaryFacilitator;
}
}
- public void evictAll() {
+ public void closeDictionaries() {
synchronized (mLock) {
- mLruCache.evictAll();
- mCachedLocales.clear();
+ mDictionaryFacilitator.closeDictionaries();
}
}
-
- @UsedForTesting
- HashSet<Locale> getCachedLocalesForTesting() {
- return mCachedLocales;
- }
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 8c780027b..064d79b3c 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -164,12 +164,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
private void asyncExecuteTaskWithWriteLock(final Runnable task) {
- asyncExecuteTaskWithLock(mLock.writeLock(), mDictName /* executorName */, task);
+ asyncExecuteTaskWithLock(mLock.writeLock(), task);
}
- private static void asyncExecuteTaskWithLock(final Lock lock, final String executorName,
- final Runnable task) {
- ExecutorUtils.getExecutor(executorName).execute(new Runnable() {
+ private static void asyncExecuteTaskWithLock(final Lock lock, final Runnable task) {
+ ExecutorUtils.getExecutorForDynamicLanguageModelUpdate().execute(new Runnable() {
@Override
public void run() {
lock.lock();
@@ -663,7 +662,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final String dictName = mDictName;
final File dictFile = mDictFile;
final AsyncResultHolder<DictionaryStats> result = new AsyncResultHolder<>();
- asyncExecuteTaskWithLock(mLock.readLock(), dictName /* executorName */, new Runnable() {
+ asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
@Override
public void run() {
final BinaryDictionary binaryDictionary = getBinaryDictionary();
@@ -714,7 +713,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
reloadDictionaryIfRequired();
final String tag = TAG;
final String dictName = mDictName;
- asyncExecuteTaskWithLock(mLock.readLock(), "dumpAllWordsForDebug", new Runnable() {
+ asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
@Override
public void run() {
Log.d(tag, "Dump dictionary: " + dictName + " for " + mLocale);
@@ -752,7 +751,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
public WordProperty[] getWordPropertiesForSyncing() {
reloadDictionaryIfRequired();
final AsyncResultHolder<WordProperty[]> result = new AsyncResultHolder<>();
- asyncExecuteTaskWithLock(mLock.readLock(), "sync-read", new Runnable() {
+ asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
@Override
public void run() {
final ArrayList<WordProperty> wordPropertyList = new ArrayList<>();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index e7917ab90..550efa59f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -127,7 +127,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final Settings mSettings;
private final DictionaryFacilitator mDictionaryFacilitator =
- DictionaryFacilitatorProvider.newDictionaryFacilitator();
+ DictionaryFacilitatorProvider.getDictionaryFacilitator();
final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
this /* SuggestionStripViewAccessor */, mDictionaryFacilitator);
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
diff --git a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
index db5e632ae..5c3abd2db 100644
--- a/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java
@@ -20,6 +20,7 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Process;
import android.util.Log;
@@ -35,6 +36,22 @@ import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils;
* package has been replaced by a newer version of the same package. This class also detects
* {@link Intent#ACTION_BOOT_COMPLETED} and {@link Intent#ACTION_USER_INITIALIZE} broadcast intent.
*
+ * If this IME has already been installed in the system image and a new version of this IME has
+ * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver and it
+ * will hide the setup wizard's icon.
+ *
+ * If this IME has already been installed in the data partition and a new version of this IME has
+ * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver but it
+ * will not hide the setup wizard's icon, and the icon will appear on the launcher.
+ *
+ * If this IME hasn't been installed yet and has been newly installed, no
+ * {@link Intent#ACTION_MY_PACKAGE_REPLACED} will be sent and the setup wizard's icon will appear
+ * on the launcher.
+ *
+ * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is received by this
+ * receiver and it checks whether the setup wizard's icon should be appeared or not on the launcher
+ * depending on which partition this IME is installed.
+ *
* When the system locale has been changed, {@link Intent#ACTION_LOCALE_CHANGED} is received by
* this receiver and the {@link KeyboardLayoutSet}'s cache is cleared.
*/
@@ -52,21 +69,22 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes();
richImm.setAdditionalInputMethodSubtypes(additionalSubtypes);
- showAppIcon(context);
+ toggleAppIcon(context);
} else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) {
Log.i(TAG, "Boot has been completed");
- showAppIcon(context);
+ toggleAppIcon(context);
} else if (Intent.ACTION_LOCALE_CHANGED.equals(intentAction)) {
Log.i(TAG, "System locale changed");
KeyboardLayoutSet.onSystemLocaleChanged();
}
// The process that hosts this broadcast receiver is invoked and remains alive even after
- // 1) the package has been re-installed, 2) the device has just booted,
+ // 1) the package has been re-installed,
+ // 2) the device has just booted,
// 3) a new user has been created.
// There is no good reason to keep the process alive if this IME isn't a current IME.
- final InputMethodManager imm =
- (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ final InputMethodManager imm = (InputMethodManager)
+ context.getSystemService(Context.INPUT_METHOD_SERVICE);
// Called to check whether this IME has been triggered by the current user or not
final boolean isInputMethodManagerValidForUserOfThisProcess =
!imm.getInputMethodList().isEmpty();
@@ -79,12 +97,17 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
}
}
- private static void showAppIcon(final Context context) {
- final ComponentName setupWizardActivity = new ComponentName(context, SetupActivity.class);
- final PackageManager pm = context.getPackageManager();
- pm.setComponentEnabledSetting(
- setupWizardActivity,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ private static void toggleAppIcon(final Context context) {
+ final int appInfoFlags = context.getApplicationInfo().flags;
+ final boolean isSystemApp = (appInfoFlags & ApplicationInfo.FLAG_SYSTEM) > 0;
+ if (Log.isLoggable(TAG, Log.INFO)) {
+ Log.i(TAG, "toggleAppIcon() : FLAG_SYSTEM = " + isSystemApp);
+ }
+ context.getPackageManager().setComponentEnabledSetting(
+ new ComponentName(context, SetupActivity.class),
+ isSystemApp
+ ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 9154cc35a..56be23f5b 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -1007,7 +1007,8 @@ public final class InputLogic {
mWordComposer.reset();
mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
if (!TextUtils.isEmpty(rejectedSuggestion)) {
- mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);
+ unlearnWord(rejectedSuggestion, inputTransaction.mSettingsValues,
+ Constants.EVENT_REJECTION);
}
StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length());
} else {
@@ -1060,6 +1061,8 @@ public final class InputLogic {
}
}
+ boolean hasUnlearnedWordBeingDeleted = false;
+
// No cancelling of commit/double space/swap: we have a regular backspace.
// We should backspace one char and restart suggestion if at the end of a word.
if (mConnection.hasSelection()) {
@@ -1090,6 +1093,11 @@ public final class InputLogic {
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
int totalDeletedLength = 1;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
+ // If this is an accelerated (i.e., double) deletion, then we need to
+ // consider unlearning here too because we may have just entered the
+ // previous word, and the next deletion will currupt it.
+ hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
+ inputTransaction.mSettingsValues, currentKeyboardScriptId);
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
totalDeletedLength++;
}
@@ -1112,6 +1120,11 @@ public final class InputLogic {
mConnection.deleteSurroundingText(lengthToDelete, 0);
int totalDeletedLength = lengthToDelete;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
+ // If this is an accelerated (i.e., double) deletion, then we need to
+ // consider unlearning here too because we may have just entered the
+ // previous word, and the next deletion will currupt it.
+ hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
+ inputTransaction.mSettingsValues, currentKeyboardScriptId);
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
@@ -1124,6 +1137,11 @@ public final class InputLogic {
StatsUtils.onBackspacePressed(totalDeletedLength);
}
}
+ if (!hasUnlearnedWordBeingDeleted) {
+ // Consider unlearning the word being deleted (if we have not done so already).
+ unlearnWordBeingDeleted(
+ inputTransaction.mSettingsValues, currentKeyboardScriptId);
+ }
if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
&& inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mCurrentLanguageHasSpaces
@@ -1135,6 +1153,38 @@ 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.
+ if (!mConnection.hasSelection()
+ && settingsValues.isSuggestionsEnabledPerUserSettings()
+ && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
+ && !mConnection.isCursorFollowedByWordCharacter(
+ settingsValues.mSpacingAndPunctuations)) {
+ final TextRange range = mConnection.getWordRangeAtCursor(
+ settingsValues.mSpacingAndPunctuations,
+ currentKeyboardScriptId);
+ final String wordBeingDeleted = range.mWord.toString();
+ if (!wordBeingDeleted.isEmpty()) {
+ unlearnWord(wordBeingDeleted, settingsValues,
+ Constants.EVENT_BACKSPACE);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void unlearnWord(final String word, final SettingsValues settingsValues, final int eventType) {
+ final NgramContext ngramContext = mConnection.getNgramContextFromNthPreviousWord(
+ settingsValues.mSpacingAndPunctuations, 2);
+ final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
+ System.currentTimeMillis());
+ mDictionaryFacilitator.unlearnFromUserHistory(
+ word, ngramContext, timeStampInSeconds, eventType);
+ }
+
/**
* Handle a press on the language switch key (the "globe key")
*/
@@ -1546,7 +1596,8 @@ public final class InputLogic {
}
mConnection.deleteSurroundingText(deleteLength, 0);
if (!TextUtils.isEmpty(committedWord)) {
- mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString);
+ unlearnWord(committedWordString, inputTransaction.mSettingsValues,
+ Constants.EVENT_REVERT);
}
final String stringToCommit = originallyTypedWord +
(usePhantomSpace ? "" : separatorString);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 95293bf2f..d35d1f2f5 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -140,8 +140,8 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
@Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
- final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
- mDictionaryFacilitatorCache.setUseContactsDictionary(useContactsDictionary);
+ final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
+ mDictionaryFacilitatorCache.setUseContactsDictionary(useContactsDictionary);
}
@Override
@@ -226,7 +226,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public boolean onUnbind(final Intent intent) {
mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
try {
- mDictionaryFacilitatorCache.evictAll();
+ mDictionaryFacilitatorCache.closeDictionaries();
} finally {
mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java b/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java
index baff8f066..856f16a53 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/UserDictionaryLookup.java
@@ -26,14 +26,13 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -83,12 +82,6 @@ public class UserDictionaryLookup implements Closeable {
private final ContentResolver mResolver;
/**
- * Executor on which to perform the initial load and subsequent reloads (after a delay).
- */
- private final ScheduledExecutorService mLoadExecutor =
- Executors.newSingleThreadScheduledExecutor();
-
- /**
* Runnable that calls loadUserDictionary().
*/
private class UserDictionaryLoader implements Runnable {
@@ -150,7 +143,8 @@ public class UserDictionaryLookup implements Closeable {
}
// Schedule a new reload after RELOAD_DELAY_MS.
- mReloadFuture = mLoadExecutor.schedule(mLoader, RELOAD_DELAY_MS, TimeUnit.MILLISECONDS);
+ mReloadFuture = ExecutorUtils.getExecutorForDynamicLanguageModelUpdate().schedule(
+ mLoader, RELOAD_DELAY_MS, TimeUnit.MILLISECONDS);
}
}
private final ContentObserver mObserver = new UserDictionaryContentObserver();
@@ -192,7 +186,7 @@ public class UserDictionaryLookup implements Closeable {
// Schedule the initial load to run immediately. It's possible that the first call to
// isValidWord occurs before the dictionary has actually loaded, so it should not
// assume that the dictionary has been loaded.
- mLoadExecutor.schedule(mLoader, 0, TimeUnit.MILLISECONDS);
+ ExecutorUtils.getExecutorForDynamicLanguageModelUpdate().execute(mLoader);
// Register the observer to be notified on changes to the UserDictionary and all individual
// items.
@@ -236,9 +230,6 @@ public class UserDictionaryLookup implements Closeable {
Log.d(TAG, "Close called (no pun intended), cleaning up executor and observer");
}
if (mIsClosed.compareAndSet(false, true)) {
- // Shut down the load executor.
- mLoadExecutor.shutdown();
-
// Unregister the content observer.
mResolver.unregisterContentObserver(mObserver);
}
@@ -342,8 +333,7 @@ public class UserDictionaryLookup implements Closeable {
if (DEBUG) {
Log.d(TAG, "Loading UserDictionary");
}
- HashMap<String, ArrayList<Locale>> dictWords =
- new HashMap<String, ArrayList<Locale>>();
+ HashMap<String, ArrayList<Locale>> dictWords = 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,
@@ -413,7 +403,7 @@ public class UserDictionaryLookup implements Closeable {
Log.d(TAG, "Word [" + dictWord +
"] not seen for other locales, creating new entry");
}
- dictLocales = new ArrayList<Locale>();
+ dictLocales = new ArrayList<>();
dictWords.put(dictWord, dictLocales);
}
// Append the locale to the list of locales this word is in.
diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
index 50be16072..c533a6273 100644
--- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -16,10 +16,12 @@
package com.android.inputmethod.latin.utils;
+import android.util.Log;
+
import com.android.inputmethod.annotations.UsedForTesting;
+import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
@@ -28,33 +30,49 @@ import java.util.concurrent.ThreadFactory;
* Utilities to manage executors.
*/
public class ExecutorUtils {
- static final ConcurrentHashMap<String, ScheduledExecutorService> sExecutorMap =
+
+ private static final String STATIC_LANGUAGE_MODEL_UPDATE = "StaticLanguageModelUpdate";
+ private static final String DYNAMIC_LANGUAGE_MODEL_UPDATE = "DynamicLanguageModelUpdate";
+
+ private static final ConcurrentHashMap<String, ScheduledExecutorService> sExecutorMap =
new ConcurrentHashMap<>();
- private static class ThreadFactoryWithId implements ThreadFactory {
- private final String mId;
+ @UsedForTesting
+ private static ScheduledExecutorService sExecutorServiceForTests;
- public ThreadFactoryWithId(final String id) {
- mId = id;
- }
+ @UsedForTesting
+ public static void setExecutorServiceForTests(
+ final ScheduledExecutorService executorServiceForTests) {
+ sExecutorServiceForTests = executorServiceForTests;
+ }
- @Override
- public Thread newThread(final Runnable r) {
- return new Thread(r, "Executor - " + mId);
- }
+ /**
+ * @return scheduled executor service used to update static language models
+ */
+ public static ScheduledExecutorService getExecutorForStaticLanguageModelUpdate() {
+ return getExecutor(STATIC_LANGUAGE_MODEL_UPDATE);
+ }
+
+ /**
+ * @return scheduled executor service used to update dynamic language models
+ */
+ public static ScheduledExecutorService getExecutorForDynamicLanguageModelUpdate() {
+ return getExecutor(DYNAMIC_LANGUAGE_MODEL_UPDATE);
}
/**
* Gets the executor for the given id.
*/
- public static ScheduledExecutorService getExecutor(final String id) {
+ private static ScheduledExecutorService getExecutor(final String id) {
+ if (sExecutorServiceForTests != null) {
+ return sExecutorServiceForTests;
+ }
ScheduledExecutorService executor = sExecutorMap.get(id);
if (executor == null) {
synchronized (sExecutorMap) {
executor = sExecutorMap.get(id);
if (executor == null) {
- executor = Executors.newSingleThreadScheduledExecutor(
- new ThreadFactoryWithId(id));
+ executor = Executors.newSingleThreadScheduledExecutor(new ExecutorFactory(id));
sExecutorMap.put(id, executor);
}
}
@@ -69,14 +87,42 @@ public class ExecutorUtils {
public static void shutdownAllExecutors() {
synchronized (sExecutorMap) {
for (final ScheduledExecutorService executor : sExecutorMap.values()) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- executor.shutdown();
- sExecutorMap.remove(executor);
- }
- });
+ executor.execute(new ExecutorShutdown(executor));
}
+ sExecutorMap.clear();
+ }
+ }
+
+ private static class ExecutorFactory implements ThreadFactory {
+ private final String mThreadName;
+
+ public ExecutorFactory(final String threadName) {
+ mThreadName = threadName;
+ }
+
+ @Override
+ public Thread newThread(final Runnable runnable) {
+ Thread thread = new Thread(runnable, mThreadName);
+ thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+ Log.w(mThreadName + "-" + runnable.getClass().getSimpleName(), ex);
+ }
+ });
+ return thread;
+ }
+ }
+
+ private static class ExecutorShutdown implements Runnable {
+ private final ScheduledExecutorService mExecutor;
+
+ public ExecutorShutdown(final ScheduledExecutorService executor) {
+ mExecutor = executor;
+ }
+
+ @Override
+ public void run() {
+ mExecutor.shutdown();
}
}
}