diff options
author | 2012-07-04 15:38:14 +0900 | |
---|---|---|
committer | 2012-07-04 15:38:21 +0900 | |
commit | c68b37964b083015967ce290991ad69d29a4055d (patch) | |
tree | 909bddc0f0c699b6344015e7ff67e31c0a1f4eff /java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java | |
parent | 1e094ac19b99c47f0b8a5108e20949ac91cfa03e (diff) | |
parent | 30a324a58dbe1e2dc47d83c1bcc0af262ab0d542 (diff) | |
download | latinime-c68b37964b083015967ce290991ad69d29a4055d.tar.gz latinime-c68b37964b083015967ce290991ad69d29a4055d.tar.xz latinime-c68b37964b083015967ce290991ad69d29a4055d.zip |
Merge remote-tracking branch 'goog/master' into mergescript
Conflicts:
CleanSpec.mk
java/Android.mk
java/res/drawable-large-hdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-large-hdpi/hint_popup_holo.9.png
java/res/drawable-large-hdpi/sym_keyboard_numsymbol_holo.png
java/res/drawable-large-hdpi/sym_keyboard_tab_holo.png
java/res/drawable-large-land-hdpi/hint_popup_holo.9.png
java/res/drawable-large-land-mdpi/hint_popup_holo.9.png
java/res/drawable-large-land-xhdpi/hint_popup_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_normal_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_normal_off_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_normal_on_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_pressed_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_light_normal_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_light_pressed_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-large-mdpi/hint_popup_holo.9.png
java/res/drawable-large-mdpi/keyboard_background_holo.9.png
java/res/drawable-large-mdpi/keyboard_popup_panel_background_holo.9.png
java/res/drawable-large-mdpi/keyboard_suggest_strip_holo.9.png
java/res/drawable-large-mdpi/sym_keyboard_delete_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num0_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num1_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num2_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num3_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num4_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num5_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num6_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num7_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num8_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num9_holo.png
java/res/drawable-large-mdpi/sym_keyboard_numbpound_holo.png
java/res/drawable-large-mdpi/sym_keyboard_numbstar_holo.png
java/res/drawable-large-mdpi/sym_keyboard_numsymbol_holo.png
java/res/drawable-large-mdpi/sym_keyboard_return_holo.png
java/res/drawable-large-mdpi/sym_keyboard_settings_holo.png
java/res/drawable-large-mdpi/sym_keyboard_shift_holo.png
java/res/drawable-large-mdpi/sym_keyboard_shift_locked_holo.png
java/res/drawable-large-mdpi/sym_keyboard_space_holo.png
java/res/drawable-large-mdpi/sym_keyboard_tab_holo.png
java/res/drawable-large-mdpi/sym_keyboard_voice_holo.png
java/res/drawable-large-mdpi/sym_keyboard_voice_off_holo.png
java/res/drawable-large-xhdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-large-xhdpi/hint_popup_holo.9.png
java/res/drawable-large-xhdpi/sym_keyboard_numsymbol_holo.png
java/res/drawable-large-xhdpi/sym_keyboard_tab_holo.png
java/res/drawable-xlarge-hdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-xlarge-hdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-land-hdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-land-mdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-land-xhdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_normal_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_normal_off_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_normal_on_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_pressed_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_light_normal_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_light_pressed_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-xlarge-mdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-mdpi/keyboard_background_holo.9.png
java/res/drawable-xlarge-mdpi/keyboard_popup_panel_background_holo.9.png
java/res/drawable-xlarge-mdpi/keyboard_suggest_strip_holo.9.png
java/res/drawable-xlarge-mdpi/sym_keyboard_delete_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num0_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num1_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num2_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num3_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num4_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num5_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num6_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num7_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num8_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num9_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_numbpound_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_numbstar_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_return_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_settings_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_shift_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_shift_locked_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_space_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_voice_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_voice_off_holo.png
java/res/drawable-xlarge-xhdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-xlarge-xhdpi/hint_popup_holo.9.png
java/res/layout-xlarge/recognition_status.xml
java/res/values-af/strings.xml
java/res/values-am/strings.xml
java/res/values-ar/strings.xml
java/res/values-be/strings.xml
java/res/values-bg/strings.xml
java/res/values-ca/strings.xml
java/res/values-cs/strings.xml
java/res/values-da/strings.xml
java/res/values-de/strings.xml
java/res/values-el/strings.xml
java/res/values-en-rGB/strings.xml
java/res/values-es-rUS/strings.xml
java/res/values-es/strings.xml
java/res/values-et/strings.xml
java/res/values-fa/strings.xml
java/res/values-fi/strings.xml
java/res/values-fr/strings.xml
java/res/values-hi/strings.xml
java/res/values-hr/strings.xml
java/res/values-hu/strings.xml
java/res/values-in/strings.xml
java/res/values-it/strings.xml
java/res/values-iw/strings.xml
java/res/values-ja/strings.xml
java/res/values-ko/strings.xml
java/res/values-large/donottranslate.xml
java/res/values-lt/strings.xml
java/res/values-lv/strings.xml
java/res/values-ms/strings.xml
java/res/values-nb/strings.xml
java/res/values-nl/strings.xml
java/res/values-pl/strings.xml
java/res/values-pt-rPT/strings.xml
java/res/values-pt/strings.xml
java/res/values-rm/strings.xml
java/res/values-ro/strings.xml
java/res/values-ru/strings.xml
java/res/values-sk/strings.xml
java/res/values-sl/strings.xml
java/res/values-sr/strings.xml
java/res/values-sv/strings.xml
java/res/values-sw/strings.xml
java/res/values-sw600dp/donottranslate.xml
java/res/values-sw768dp/donottranslate.xml
java/res/values-th/strings.xml
java/res/values-tl/strings.xml
java/res/values-tr/strings.xml
java/res/values-uk/strings.xml
java/res/values-vi/strings.xml
java/res/values-xlarge/donottranslate.xml
java/res/values-zh-rCN/strings.xml
java/res/values-zh-rTW/strings.xml
java/res/values-zu/strings.xml
java/res/values/keypress-vibration-durations.xml
java/res/values/predefined-subtypes.xml
java/res/xml-large-land/kbd_popup_template.xml
java/res/xml-large/kbd_key_styles.xml
java/res/xml-large/kbd_popup_template.xml
java/res/xml-large/kbd_qwerty_f2.xml
java/res/xml-large/kbd_qwerty_row1.xml
java/res/xml-large/kbd_qwerty_row2.xml
java/res/xml-large/kbd_qwerty_row3.xml
java/res/xml-large/kbd_qwerty_row4.xml
java/res/xml-large/kbd_row3_right.xml
java/res/xml-large/kbd_rows_arabic.xml
java/res/xml-large/kbd_rows_azerty.xml
java/res/xml-large/kbd_rows_hebrew.xml
java/res/xml-large/kbd_rows_qwerty.xml
java/res/xml-large/kbd_rows_qwertz.xml
java/res/xml-large/kbd_rows_russian.xml
java/res/xml-large/kbd_rows_scandinavian.xml
java/res/xml-large/kbd_rows_serbian.xml
java/res/xml-large/kbd_rows_spanish.xml
java/res/xml-large/kbd_symbols.xml
java/res/xml-large/kbd_symbols_shift.xml
java/res/xml-sw600dp-land/kbd_more_keys_keyboard_template.xml
java/res/xml-sw600dp-land/kbd_popup_template.xml
java/res/xml-sw600dp/kbd_more_keys_keyboard_template.xml
java/res/xml-sw600dp/kbd_popup_template.xml
java/res/xml-sw600dp/kbd_row3_right.xml
java/res/xml-sw600dp/kbd_rows_qwerty.xml
java/res/xml-sw600dp/keys_comma_period.xml
java/res/xml-sw768dp-land/kbd_more_keys_keyboard_template.xml
java/res/xml-sw768dp-land/kbd_popup_template.xml
java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
java/res/xml-sw768dp/kbd_popup_template.xml
java/res/xml-sw768dp/kbd_row3_right2.xml
java/res/xml-sw768dp/kbd_rows_qwerty.xml
java/res/xml-sw768dp/row_symbols_shift4.xml
java/res/xml-xlarge-land/kbd_popup_template.xml
java/res/xml-xlarge/kbd_key_styles.xml
java/res/xml-xlarge/kbd_popup_template.xml
java/res/xml-xlarge/kbd_qwerty_row1.xml
java/res/xml-xlarge/kbd_qwerty_row2.xml
java/res/xml-xlarge/kbd_qwerty_row3.xml
java/res/xml-xlarge/kbd_qwerty_row4.xml
java/res/xml-xlarge/kbd_row3_right2.xml
java/res/xml-xlarge/kbd_rows_arabic.xml
java/res/xml-xlarge/kbd_rows_azerty.xml
java/res/xml-xlarge/kbd_rows_hebrew.xml
java/res/xml-xlarge/kbd_rows_qwerty.xml
java/res/xml-xlarge/kbd_rows_qwertz.xml
java/res/xml-xlarge/kbd_rows_russian.xml
java/res/xml-xlarge/kbd_rows_scandinavian.xml
java/res/xml-xlarge/kbd_rows_serbian.xml
java/res/xml-xlarge/kbd_rows_spanish.xml
java/res/xml-xlarge/kbd_symbols.xml
java/res/xml-xlarge/kbd_symbols_shift.xml
java/res/xml/key_azerty_quote.xml
java/res/xml/key_f1.xml
java/res/xml/method.xml
java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
java/src/com/android/inputmethod/latin/Utils.java
native/Android.mk
Change-Id: I96e8e042f636ed8e5cc023cf8514f13121e39195
Diffstat (limited to '')
-rw-r--r-- | java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java | 362 |
1 files changed, 328 insertions, 34 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 7ce92920d..063243e1b 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -17,13 +17,14 @@ package com.android.inputmethod.latin; import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetFileDescriptor; import android.util.Log; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; import java.util.Locale; /** @@ -36,13 +37,127 @@ class BinaryDictionaryGetter { */ private static final String TAG = BinaryDictionaryGetter.class.getSimpleName(); + /** + * Used to return empty lists + */ + private static final File[] EMPTY_FILE_ARRAY = new File[0]; + + /** + * Name of the common preferences name to know which word list are on and which are off. + */ + private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs"; + + // Name of the category for the main dictionary + private static final String MAIN_DICTIONARY_CATEGORY = "main"; + public static final String ID_CATEGORY_SEPARATOR = ":"; + // Prevents this from being instantiated private BinaryDictionaryGetter() {} /** + * Returns whether we may want to use this character as part of a file name. + * + * This basically only accepts ascii letters and numbers, and rejects everything else. + */ + private static boolean isFileNameCharacter(int codePoint) { + if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit + if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase + if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase + return codePoint == '_'; // Underscore + } + + /** + * Escapes a string for any characters that may be suspicious for a file or directory name. + * + * Concretely this does a sort of URL-encoding except it will encode everything that's not + * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which + * we cannot allow here) + */ + // TODO: create a unit test for this method + private static String replaceFileNameDangerousCharacters(final String name) { + // This assumes '%' is fully available as a non-separator, normal + // character in a file name. This is probably true for all file systems. + final StringBuilder sb = new StringBuilder(); + final int nameLength = name.length(); + for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) { + final int codePoint = name.codePointAt(i); + if (isFileNameCharacter(codePoint)) { + sb.appendCodePoint(codePoint); + } else { + // 6 digits - unicode is limited to 21 bits + sb.append(String.format((Locale)null, "%%%1$06x", codePoint)); + } + } + return sb.toString(); + } + + /** + * Reverse escaping done by replaceFileNameDangerousCharacters. + */ + private static String getWordListIdFromFileName(final String fname) { + final StringBuilder sb = new StringBuilder(); + final int fnameLength = fname.length(); + for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) { + final int codePoint = fname.codePointAt(i); + if ('%' != codePoint) { + sb.appendCodePoint(codePoint); + } else { + final int encodedCodePoint = Integer.parseInt(fname.substring(i + 1, i + 7), 16); + i += 6; + sb.appendCodePoint(encodedCodePoint); + } + } + return sb.toString(); + } + + /** + * Helper method to get the top level cache directory. + */ + private static String getWordListCacheDirectory(final Context context) { + return context.getFilesDir() + File.separator + "dicts"; + } + + /** + * Find out the cache directory associated with a specific locale. + */ + private static String getCacheDirectoryForLocale(final String locale, final Context context) { + final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale); + final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator + + relativeDirectoryName; + final File directory = new File(absoluteDirectoryName); + if (!directory.exists()) { + if (!directory.mkdirs()) { + Log.e(TAG, "Could not create the directory for locale" + locale); + } + } + return absoluteDirectoryName; + } + + /** + * Generates a file name for the id and locale passed as an argument. + * + * In the current implementation the file name returned will always be unique for + * any id/locale pair, but please do not expect that the id can be the same for + * different dictionaries with different locales. An id should be unique for any + * dictionary. + * The file name is pretty much an URL-encoded version of the id inside a directory + * named like the locale, except it will also escape characters that look dangerous + * to some file systems. + * @param id the id of the dictionary for which to get a file name + * @param locale the locale for which to get the file name as a string + * @param context the context to use for getting the directory + * @return the name of the file to be created + */ + public static String getCacheFileName(String id, String locale, Context context) { + final String fileName = replaceFileNameDangerousCharacters(id); + return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; + } + + /** * Returns a file address from a resource, or null if it cannot be opened. */ - private static AssetFileAddress loadFallbackResource(Context context, int fallbackResId) { + private static AssetFileAddress loadFallbackResource(final Context context, + final int fallbackResId) { final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId); if (afd == null) { Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId=" @@ -53,45 +168,224 @@ class BinaryDictionaryGetter { context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength()); } + static private class DictPackSettings { + final SharedPreferences mDictPreferences; + public DictPackSettings(final Context context) { + Context dictPackContext = null; + try { + final String dictPackName = + context.getString(R.string.dictionary_pack_package_name); + dictPackContext = context.createPackageContext(dictPackName, 0); + } catch (NameNotFoundException e) { + // The dictionary pack is not installed... + // TODO: fallback on the built-in dict, see the TODO above + Log.e(TAG, "Could not find a dictionary pack"); + } + mDictPreferences = null == dictPackContext ? null + : dictPackContext.getSharedPreferences(COMMON_PREFERENCES_NAME, + Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS); + } + public boolean isWordListActive(final String dictId) { + if (null == mDictPreferences) { + // If we don't have preferences it basically means we can't find the dictionary + // pack - either it's not installed, or it's disabled, or there is some strange + // bug. Either way, a word list with no settings should be on by default: default + // dictionaries in LatinIME are on if there is no settings at all, and if for some + // reason some dictionaries have been installed BUT the dictionary pack can't be + // found anymore it's safer to actually supply installed dictionaries. + return true; + } else { + // The default is true here for the same reasons as above. We got the dictionary + // pack but if we don't have any settings for it it means the user has never been + // to the settings yet. So by default, the main dictionaries should be on. + return mDictPreferences.getBoolean(dictId, true); + } + } + } + + /** + * Helper method to the list of cache directories, one for each distinct locale. + */ + private static File[] getCachedDirectoryList(final Context context) { + return new File(getWordListCacheDirectory(context)).listFiles(); + } + + /** + * Returns the category for a given file name. + * + * This parses the file name, extracts the category, and returns it. See + * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}. + * @return The category as a string or null if it can't be found in the file name. + */ + private static String getCategoryFromFileName(final String fileName) { + final String id = getWordListIdFromFileName(fileName); + final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); + if (2 != idArray.length) return null; + return idArray[0]; + } + + /** + * Utility class for the {@link #getCachedWordLists} method + */ + private static class FileAndMatchLevel { + final File mFile; + final int mMatchLevel; + public FileAndMatchLevel(final File file, final int matchLevel) { + mFile = file; + mMatchLevel = matchLevel; + } + } + + /** + * Returns the list of cached files for a specific locale, one for each category. + * + * This will return exactly one file for each word list category that matches + * the passed locale. If several files match the locale for any given category, + * this returns the file with the closest match to the locale. For example, if + * the passed word list is en_US, and for a category we have an en and an en_US + * word list available, we'll return only the en_US one. + * Thus, the list will contain as many files as there are categories. + * + * @param locale the locale to find the dictionary files for, as a string. + * @param context the context on which to open the files upon. + * @return an array of binary dictionary files, which may be empty but may not be null. + */ + private static File[] getCachedWordLists(final String locale, + final Context context) { + final File[] directoryList = getCachedDirectoryList(context); + if (null == directoryList) return EMPTY_FILE_ARRAY; + final HashMap<String, FileAndMatchLevel> cacheFiles = + new HashMap<String, FileAndMatchLevel>(); + for (File directory : directoryList) { + if (!directory.isDirectory()) continue; + final String dirLocale = getWordListIdFromFileName(directory.getName()); + final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale); + if (LocaleUtils.isMatch(matchLevel)) { + final File[] wordLists = directory.listFiles(); + if (null != wordLists) { + for (File wordList : wordLists) { + final String category = getCategoryFromFileName(wordList.getName()); + final FileAndMatchLevel currentBestMatch = cacheFiles.get(category); + if (null == currentBestMatch || currentBestMatch.mMatchLevel < matchLevel) { + cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel)); + } + } + } + } + } + if (cacheFiles.isEmpty()) return EMPTY_FILE_ARRAY; + final File[] result = new File[cacheFiles.size()]; + int index = 0; + for (final FileAndMatchLevel entry : cacheFiles.values()) { + result[index++] = entry.mFile; + } + return result; + } + + /** + * Remove all files with the passed id, except the passed file. + * + * If a dictionary with a given ID has a metadata change that causes it to change + * path, we need to remove the old version. The only way to do this is to check all + * installed files for a matching ID in a different directory. + */ + public static void removeFilesWithIdExcept(final Context context, final String id, + final File fileToKeep) { + try { + final File canonicalFileToKeep = fileToKeep.getCanonicalFile(); + final File[] directoryList = getCachedDirectoryList(context); + if (null == directoryList) return; + for (File directory : directoryList) { + // There is one directory per locale. See #getCachedDirectoryList + if (!directory.isDirectory()) continue; + final File[] wordLists = directory.listFiles(); + if (null == wordLists) continue; + for (File wordList : wordLists) { + final String fileId = getWordListIdFromFileName(wordList.getName()); + if (fileId.equals(id)) { + if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) { + wordList.delete(); + } + } + } + } + } catch (java.io.IOException e) { + Log.e(TAG, "IOException trying to cleanup files : " + e); + } + } + + + /** + * Returns the id associated with the main word list for a specified locale. + * + * Word lists stored in Android Keyboard's resources are referred to as the "main" + * word lists. Since they can be updated like any other list, we need to assign a + * unique ID to them. This ID is just the name of the language (locale-wise) they + * are for, and this method returns this ID. + */ + private static String getMainDictId(final Locale locale) { + // This works because we don't include by default different dictionaries for + // different countries. This actually needs to return the id that we would + // like to use for word lists included in resources, and the following is okay. + return MAIN_DICTIONARY_CATEGORY + ID_CATEGORY_SEPARATOR + locale.getLanguage().toString(); + } + + private static boolean isMainWordListId(final String id) { + final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); + if (2 != idArray.length) return false; + return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); + } + /** * Returns a list of file addresses for a given locale, trying relevant methods in order. * * Tries to get binary dictionaries from various sources, in order: - * - Uses a private method of getting a private dictionaries, as implemented by the - * PrivateBinaryDictionaryGetter class. - * If that fails: * - Uses a content provider to get a public dictionary set, as per the protocol described * in BinaryDictionaryFileDumper. * If that fails: - * - Gets a file name from the fallback resource passed as an argument. + * - Gets a file name from the built-in dictionary for this locale, if any. * If that fails: * - Returns null. - * @return The address of a valid file, or null. - */ - public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context, - int fallbackResId) { - // Try first to query a private package signed the same way for private files. - final List<AssetFileAddress> privateFiles = - PrivateBinaryDictionaryGetter.getDictionaryFiles(locale, context); - if (null != privateFiles) { - return privateFiles; - } else { - try { - // If that was no-go, try to find a publicly exported dictionary. - List<AssetFileAddress> listFromContentProvider = - BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context); - if (null != listFromContentProvider) { - return listFromContentProvider; - } - // If the list is null, fall through and return the fallback - } catch (FileNotFoundException e) { - Log.e(TAG, "Unable to create dictionary file from provider for locale " - + locale.toString() + ": falling back to internal dictionary"); - } catch (IOException e) { - Log.e(TAG, "Unable to read source data for locale " - + locale.toString() + ": falling back to internal dictionary"); + * @return The list of addresses of valid dictionary files, or null. + */ + public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale, + final Context context) { + + final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale); + // cacheWordListsFromContentProvider returns the list of files it copied to local + // storage, but we don't really care about what was copied NOW: what we want is the + // list of everything we ever cached, so we ignore the return value. + BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, + hasDefaultWordList); + final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); + final String mainDictId = getMainDictId(locale); + final DictPackSettings dictPackSettings = new DictPackSettings(context); + + boolean foundMainDict = false; + final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>(); + // cachedWordLists may not be null, see doc for getCachedDictionaryList + for (final File f : cachedWordLists) { + final String wordListId = getWordListIdFromFileName(f.getName()); + if (isMainWordListId(wordListId)) { + foundMainDict = true; + } + if (!dictPackSettings.isWordListActive(wordListId)) continue; + if (f.canRead()) { + fileList.add(AssetFileAddress.makeFromFileName(f.getPath())); + } else { + Log.e(TAG, "Found a cached dictionary file but cannot read it"); + } + } + + if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) { + final int fallbackResId = + DictionaryFactory.getMainDictionaryResourceId(context.getResources(), locale); + final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId); + if (null != fallbackAsset) { + fileList.add(fallbackAsset); } - return Arrays.asList(loadFallbackResource(context, fallbackResId)); } + + return fileList; } } |