aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod')
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/ActionBatch.java3
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java4
-rw-r--r--java/src/com/android/inputmethod/latin/PersonalDictionaryLookup.java54
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java2
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java7
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java21
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java107
7 files changed, 139 insertions, 59 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index ee142d845..09f8032cc 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -377,7 +377,8 @@ public final class ActionBatch {
final ContentValues values = MetadataDbHelper.makeContentValues(0,
MetadataDbHelper.TYPE_BULK, MetadataDbHelper.STATUS_INSTALLED,
mWordList.mId, mWordList.mLocale, mWordList.mDescription,
- "", mWordList.mRemoteFilename, mWordList.mLastUpdate,
+ TextUtils.isEmpty(mWordList.mLocalFilename) ? "" : mWordList.mLocalFilename,
+ mWordList.mRemoteFilename, mWordList.mLastUpdate,
mWordList.mRawChecksum, mWordList.mChecksum, mWordList.mRetryCount,
mWordList.mFileSize, mWordList.mVersion, mWordList.mFormatVersion);
PrivateLog.log("Insert 'preinstalled' record for " + mWordList.mDescription
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index e720f3cd0..e61547a9d 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -95,6 +95,8 @@ public final class UpdateHandler {
// Name of the category for the main dictionary
public static final String MAIN_DICTIONARY_CATEGORY = "main";
+ public static final String TEMP_DICT_FILE_SUB = "___";
+
// The id for the "dictionary available" notification.
static final int DICT_AVAILABLE_NOTIFICATION_ID = 1;
@@ -743,7 +745,7 @@ public final class UpdateHandler {
throws IOException {
DebugLogUtils.l("Entering openTempFileOutput");
final File dir = context.getFilesDir();
- final File f = File.createTempFile(locale + "___", DICT_FILE_SUFFIX, dir);
+ final File f = File.createTempFile(locale + TEMP_DICT_FILE_SUB, DICT_FILE_SUFFIX, dir);
DebugLogUtils.l("File name is", f.getName());
return f.getName();
}
diff --git a/java/src/com/android/inputmethod/latin/PersonalDictionaryLookup.java b/java/src/com/android/inputmethod/latin/PersonalDictionaryLookup.java
index 1ba075c54..eed4ec1a0 100644
--- a/java/src/com/android/inputmethod/latin/PersonalDictionaryLookup.java
+++ b/java/src/com/android/inputmethod/latin/PersonalDictionaryLookup.java
@@ -40,9 +40,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -196,11 +196,10 @@ public class PersonalDictionaryLookup implements Closeable {
private AtomicBoolean mIsClosed = new AtomicBoolean(false);
/**
- * We store a map from a dictionary word to the set of locales it belongs
- * in. We then iterate over the set of locales to find a match using
- * LocaleUtils.
+ * We store a map from a dictionary word to the set of locales & raw string(as it appears)
+ * We then iterate over the set of locales to find a match using LocaleUtils.
*/
- private volatile HashMap<String, ArrayList<Locale>> mDictWords;
+ private volatile HashMap<String, HashMap<Locale, String>> mDictWords;
/**
* We store a map from a shortcut to a word for each locale.
@@ -317,7 +316,7 @@ public class PersonalDictionaryLookup implements Closeable {
* @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;
+ final HashMap<String, HashMap<Locale, String>> dictWords = mDictWords;
if (CollectionUtils.isNullOrEmpty(dictWords)) {
return Collections.emptySet();
}
@@ -325,12 +324,15 @@ public class PersonalDictionaryLookup implements Closeable {
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);
- }
+ HashMap<Locale, String> localeStringMap = dictWords.get(word);
+ if (!CollectionUtils.isNullOrEmpty(localeStringMap)) {
+ for (Locale wordLocale : localeStringMap.keySet()) {
+ final String wordLocaleString = wordLocale.toString();
+ final int match = LocaleUtils.getMatchLevel(wordLocaleString, inputLocaleString);
+ if (LocaleUtils.isMatch(match)) {
+ words.add(localeStringMap.get(wordLocale));
+ }
+ }
}
}
return words;
@@ -399,29 +401,29 @@ public class PersonalDictionaryLookup implements Closeable {
return false;
}
- // Atomically obtain the current copy of mDictWords;
- final HashMap<String, ArrayList<Locale>> dictWords = mDictWords;
-
if (DebugFlags.DEBUG_ENABLED) {
Log.d(mTag, "isValidWord() : Word [" + word + "] in Locale [" + inputLocale + "]");
}
+ // Atomically obtain the current copy of mDictWords;
+ final HashMap<String, HashMap<Locale, String>> dictWords = mDictWords;
// Lowercase the word using the given locale. Note, that dictionary
// words are lowercased using their locale, and theoretically the
// lowercasing between two matching locales may differ. For simplicity
// we ignore that possibility.
final String lowercased = word.toLowerCase(inputLocale);
- final ArrayList<Locale> dictLocales = dictWords.get(lowercased);
- if (null == dictLocales) {
+ final HashMap<Locale, String> dictLocales = dictWords.get(lowercased);
+
+ if (CollectionUtils.isNullOrEmpty(dictLocales)) {
if (DebugFlags.DEBUG_ENABLED) {
- Log.d(mTag, "isValidWord() : No entry for lowercased word [" + lowercased + "]");
+ Log.d(mTag, "isValidWord() : No entry for word [" + word + "]");
}
return false;
} else {
if (DebugFlags.DEBUG_ENABLED) {
- Log.d(mTag, "isValidWord() : Found entry for lowercased word [" + lowercased + "]");
+ Log.d(mTag, "isValidWord() : Found entry for word [" + word + "]");
}
// Iterate over the locales this word is in.
- for (final Locale dictLocale : dictLocales) {
+ for (final Locale dictLocale : dictLocales.keySet()) {
final int matchLevel = LocaleUtils.getMatchLevel(dictLocale.toString(),
inputLocale.toString());
if (DebugFlags.DEBUG_ENABLED) {
@@ -529,7 +531,7 @@ public class PersonalDictionaryLookup implements Closeable {
return;
}
Log.i(mTag, "loadPersonalDictionary() : Start Loading");
- HashMap<String, ArrayList<Locale>> dictWords = new HashMap<>();
+ HashMap<String, HashMap<Locale, String>> dictWords = new HashMap<>();
HashMap<Locale, HashMap<String, String>> shortcutsPerLocale = new HashMap<>();
// Load the dictionary. Items are returned in the default sort order (by frequency).
Cursor cursor = mResolver.query(UserDictionary.Words.CONTENT_URI,
@@ -581,21 +583,21 @@ public class PersonalDictionaryLookup implements Closeable {
final String dictWord = rawDictWord.toLowerCase(dictLocale);
if (DebugFlags.DEBUG_ENABLED) {
Log.d(mTag, "loadPersonalDictionary() : Adding word [" + dictWord
- + "] for locale " + dictLocale);
+ + "] for locale " + dictLocale + "with value" + rawDictWord);
}
// Check if there is an existing entry for this word.
- ArrayList<Locale> dictLocales = dictWords.get(dictWord);
- if (null == dictLocales) {
+ HashMap<Locale, String> dictLocales = dictWords.get(dictWord);
+ if (CollectionUtils.isNullOrEmpty(dictLocales)) {
// If there is no entry for this word, create one.
if (DebugFlags.DEBUG_ENABLED) {
Log.d(mTag, "loadPersonalDictionary() : Word [" + dictWord +
"] not seen for other locales, creating new entry");
}
- dictLocales = new ArrayList<>();
+ dictLocales = new HashMap<>();
dictWords.put(dictWord, dictLocales);
}
// Append the locale to the list of locales this word is in.
- dictLocales.add(dictLocale);
+ dictLocales.put(dictLocale, rawDictWord);
// If there is no column for a shortcut, we're done.
final int shortcutIndex = cursor.getColumnIndex(UserDictionary.Words.SHORTCUT);
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 49c47d775..a123d282b 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -48,6 +48,7 @@ import com.android.inputmethod.latin.utils.SpannableStringUtils;
import com.android.inputmethod.latin.utils.TextRange;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
/**
* Enrichment class for InputConnection to simplify interaction and add functionality.
@@ -288,6 +289,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
}
}
+ @Nullable
public CharSequence getSelectedText(final int flags) {
return isConnected() ? mIC.getSelectedText(flags) : null;
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 4bee94ad4..324ae3a19 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -1089,8 +1089,11 @@ public final class InputLogic {
// If there is a selection, remove it.
// We also need to unlearn the selected text.
final CharSequence selection = mConnection.getSelectedText(0 /* 0 for no styles */);
- unlearnWord(selection.toString(), inputTransaction.mSettingsValues,
- Constants.EVENT_BACKSPACE);
+ if (!TextUtils.isEmpty(selection)) {
+ unlearnWord(selection.toString(), inputTransaction.mSettingsValues,
+ Constants.EVENT_BACKSPACE);
+ hasUnlearnedWordBeingDeleted = true;
+ }
final int numCharsDeleted = mConnection.getExpectedSelectionEnd()
- mConnection.getExpectedSelectionStart();
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index fd5c54c09..d6de94532 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -58,6 +58,9 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
protected final SuggestionsCache mSuggestionsCache = new SuggestionsCache();
private final ContentObserver mObserver;
+ private static final String quotesRegexp =
+ "\\u0022|\\u0027|\\u0060|\\u00B4|\\u2018|\\u2018|\\u201C|\\u201D";
+
private static final class SuggestionsParams {
public final String[] mSuggestions;
public final int mFlags;
@@ -224,12 +227,16 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
protected SuggestionsInfo onGetSuggestionsInternal(
final TextInfo textInfo, final NgramContext ngramContext, final int suggestionsLimit) {
try {
- final String inText = textInfo.getText();
+ final String text = textInfo.getText().
+ replaceAll(AndroidSpellCheckerService.APOSTROPHE,
+ AndroidSpellCheckerService.SINGLE_QUOTE).
+ replaceAll("^" + quotesRegexp, "").
+ replaceAll(quotesRegexp + "$", "");
final SuggestionsParams cachedSuggestionsParams =
- mSuggestionsCache.getSuggestionsFromCache(inText, ngramContext);
+ mSuggestionsCache.getSuggestionsFromCache(text, ngramContext);
if (cachedSuggestionsParams != null) {
- Log.d(TAG, "onGetSuggestionsInternal() : Cache hit for [" + inText + "]");
+ Log.d(TAG, "onGetSuggestionsInternal() : Cache hit for [" + text + "]");
return new SuggestionsInfo(
cachedSuggestionsParams.mFlags, cachedSuggestionsParams.mSuggestions);
}
@@ -241,10 +248,10 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
// Handle special patterns like email, URI, telephone number.
- final int checkability = getCheckabilityInScript(inText, mScript);
+ final int checkability = getCheckabilityInScript(text, mScript);
if (CHECKABILITY_CHECKABLE != checkability) {
if (CHECKABILITY_CONTAINS_PERIOD == checkability) {
- final String[] splitText = inText.split(Constants.REGEXP_PERIOD);
+ final String[] splitText = text.split(Constants.REGEXP_PERIOD);
boolean allWordsAreValid = true;
for (final String word : splitText) {
if (!mService.isValidWord(mLocale, word)) {
@@ -259,15 +266,13 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
TextUtils.join(Constants.STRING_SPACE, splitText) });
}
}
- return mService.isValidWord(mLocale, inText) ?
+ return mService.isValidWord(mLocale, text) ?
AndroidSpellCheckerService.getInDictEmptySuggestions() :
AndroidSpellCheckerService.getNotInDictEmptySuggestions(
CHECKABILITY_CONTAINS_PERIOD == checkability /* reportAsTypo */);
}
// Handle normal words.
- final String text = inText.replaceAll(
- AndroidSpellCheckerService.APOSTROPHE, AndroidSpellCheckerService.SINGLE_QUOTE);
final int capitalizeType = StringUtils.getCapitalizationType(text);
if (isInDictForAnyCapitalization(text, capitalizeType)) {
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 25fa723cc..cfa977a46 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -25,6 +25,7 @@ import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.dictionarypack.UpdateHandler;
import com.android.inputmethod.latin.AssetFileAddress;
import com.android.inputmethod.latin.BinaryDictionaryGetter;
import com.android.inputmethod.latin.R;
@@ -36,6 +37,7 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import java.io.File;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
@@ -58,6 +60,8 @@ public class DictionaryInfoUtils {
// 6 digits - unicode is limited to 21 bits
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
+ private static final String TEMP_DICT_FILE_SUB = UpdateHandler.TEMP_DICT_FILE_SUB;
+
public static class DictionaryInfo {
private static final String LOCALE_COLUMN = "locale";
private static final String WORDLISTID_COLUMN = "id";
@@ -66,22 +70,24 @@ public class DictionaryInfoUtils {
private static final String DATE_COLUMN = "date";
private static final String FILESIZE_COLUMN = "filesize";
private static final String VERSION_COLUMN = "version";
- @Nonnull
- public final String mId;
- @Nonnull
- public final Locale mLocale;
- @Nullable
- public final String mDescription;
- public final AssetFileAddress mFileAddress;
+
+ @Nonnull public final String mId;
+ @Nonnull public final Locale mLocale;
+ @Nullable public final String mDescription;
+ @Nullable public final String mFilename;
+ public final long mFilesize;
+ public final long mModifiedTimeMillis;
public final int mVersion;
- public DictionaryInfo(@Nonnull final String id, @Nonnull final Locale locale,
- @Nullable final String description, @Nullable final AssetFileAddress fileAddress,
- final int version) {
+ public DictionaryInfo(@Nonnull String id, @Nonnull Locale locale,
+ @Nullable String description, @Nullable String filename,
+ long filesize, long modifiedTimeMillis, int version) {
mId = id;
mLocale = locale;
mDescription = description;
- mFileAddress = fileAddress;
+ mFilename = filename;
+ mFilesize = filesize;
+ mModifiedTimeMillis = modifiedTimeMillis;
mVersion = version;
}
@@ -90,12 +96,9 @@ public class DictionaryInfoUtils {
values.put(WORDLISTID_COLUMN, mId);
values.put(LOCALE_COLUMN, mLocale.toString());
values.put(DESCRIPTION_COLUMN, mDescription);
- values.put(LOCAL_FILENAME_COLUMN,
- mFileAddress != null ? mFileAddress.mFilename : "");
- values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(
- mFileAddress != null ? new File(mFileAddress.mFilename).lastModified() : 0));
- values.put(FILESIZE_COLUMN,
- mFileAddress != null ? mFileAddress.mLength : 0);
+ values.put(LOCAL_FILENAME_COLUMN, mFilename != null ? mFilename : "");
+ values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(mModifiedTimeMillis));
+ values.put(FILESIZE_COLUMN, mFilesize);
values.put(VERSION_COLUMN, mVersion);
return values;
}
@@ -185,6 +188,17 @@ public class DictionaryInfoUtils {
return new File(DictionaryInfoUtils.getWordListCacheDirectory(context)).listFiles();
}
+ @Nullable
+ public static File[] getUnusedDictionaryList(final Context context) {
+ return context.getFilesDir().listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String filename) {
+ return !TextUtils.isEmpty(filename) && filename.endsWith(".dict")
+ && filename.contains(TEMP_DICT_FILE_SUB);
+ }
+ });
+ }
+
/**
* Returns the category for a given file name.
*
@@ -342,12 +356,44 @@ public class DictionaryInfoUtils {
* @return information of the specified dictionary.
*/
private static DictionaryInfo createDictionaryInfoFromFileAddress(
- final AssetFileAddress fileAddress, Locale locale) {
+ @Nonnull final AssetFileAddress fileAddress, final Locale locale) {
+ final String id = getMainDictId(locale);
+ final int version = DictionaryHeaderUtils.getContentVersion(fileAddress);
+ final String description = SubtypeLocaleUtils
+ .getSubtypeLocaleDisplayName(locale.toString());
+ // Do not store the filename on db as it will try to move the filename from db to the
+ // cached directory. If the filename is already in cached directory, this is not
+ // necessary.
+ final String filenameToStoreOnDb = null;
+ return new DictionaryInfo(id, locale, description, filenameToStoreOnDb,
+ fileAddress.mLength, new File(fileAddress.mFilename).lastModified(), version);
+ }
+
+ /**
+ * Returns the information of the dictionary for the given {@link AssetFileAddress}.
+ * If the file is corrupted or a pre-fava file, then the file gets deleted and the null
+ * value is returned.
+ */
+ @Nullable
+ private static DictionaryInfo createDictionaryInfoForUnCachedFile(
+ @Nonnull final AssetFileAddress fileAddress, final Locale locale) {
final String id = getMainDictId(locale);
final int version = DictionaryHeaderUtils.getContentVersion(fileAddress);
+
+ if (version == -1) {
+ // Purge the pre-fava/corrupted unused dictionaires.
+ fileAddress.deleteUnderlyingFile();
+ return null;
+ }
+
final String description = SubtypeLocaleUtils
.getSubtypeLocaleDisplayName(locale.toString());
- return new DictionaryInfo(id, locale, description, fileAddress, version);
+
+ final File unCachedFile = new File(fileAddress.mFilename);
+ // Store just the filename and not the full path.
+ final String filenameToStoreOnDb = unCachedFile.getName();
+ return new DictionaryInfo(id, locale, description, filenameToStoreOnDb, fileAddress.mLength,
+ unCachedFile.lastModified(), version);
}
/**
@@ -358,7 +404,7 @@ public class DictionaryInfoUtils {
final int version = -1;
final String description = SubtypeLocaleUtils
.getSubtypeLocaleDisplayName(locale.toString());
- return new DictionaryInfo(id, locale, description, null, version);
+ return new DictionaryInfo(id, locale, description, null, 0L, 0L, version);
}
private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList,
@@ -380,7 +426,7 @@ public class DictionaryInfoUtils {
final Context context) {
final ArrayList<DictionaryInfo> dictList = new ArrayList<>();
- // Retrieve downloaded dictionaries
+ // Retrieve downloaded dictionaries from cached directories
final File[] directoryList = getCachedDirectoryList(context);
if (null != directoryList) {
for (final File directory : directoryList) {
@@ -407,6 +453,25 @@ public class DictionaryInfoUtils {
}
}
+ // Retrieve downloaded dictionaries from the unused dictionaries.
+ File[] unusedDictionaryList = getUnusedDictionaryList(context);
+ if (unusedDictionaryList != null) {
+ for (File dictionaryFile : unusedDictionaryList) {
+ String fileName = dictionaryFile.getName();
+ int index = fileName.indexOf(TEMP_DICT_FILE_SUB);
+ if (index == -1) {
+ continue;
+ }
+ String locale = fileName.substring(0, index);
+ DictionaryInfo dictionaryInfo = createDictionaryInfoForUnCachedFile(
+ AssetFileAddress.makeFromFile(dictionaryFile),
+ LocaleUtils.constructLocaleFromString(locale));
+ if (dictionaryInfo != null) {
+ addOrUpdateDictInfo(dictList, dictionaryInfo);
+ }
+ }
+ }
+
// Retrieve files from assets
final Resources resources = context.getResources();
final AssetManager assets = resources.getAssets();