diff options
Diffstat (limited to 'java')
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(); |