BinaryDictionaryGetter.java revision 81d97eec0e77e72cce606f9c9f96091c0b348190
1cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard/* 2cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * Copyright (C) 2011 The Android Open Source Project 3cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * 4cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * use this file except in compliance with the License. You may obtain a copy of 6cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * the License at 7cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * 8cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * http://www.apache.org/licenses/LICENSE-2.0 9cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * 10cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * Unless required by applicable law or agreed to in writing, software 11cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * License for the specific language governing permissions and limitations under 14cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * the License. 15cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard */ 16cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 17cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardpackage com.android.inputmethod.latin; 18cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 1913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalardimport com.android.inputmethod.latin.makedict.BinaryDictInputOutput; 2081d97eec0e77e72cce606f9c9f96091c0b348190Yuichiro Hanadaimport com.android.inputmethod.latin.makedict.FormatSpec; 2113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard 22cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport android.content.Context; 2386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalardimport android.content.SharedPreferences; 2486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalardimport android.content.pm.PackageManager.NameNotFoundException; 25cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport android.content.res.AssetFileDescriptor; 26cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport android.util.Log; 27cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 2828966734619251f78812f6a53f5efacbf5f77c49Jean Chalardimport java.io.File; 29d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanadaimport java.io.FileInputStream; 30d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanadaimport java.io.IOException; 31d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanadaimport java.nio.ByteBuffer; 32d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanadaimport java.nio.channels.FileChannel; 3308868624ede5eb4950972833f015d465408d3408Jean Chalardimport java.util.ArrayList; 340df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalardimport java.util.HashMap; 35cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport java.util.Locale; 36cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 37cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard/** 38cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * Helper class to get the address of a mmap'able dictionary file. 39cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard */ 40cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardclass BinaryDictionaryGetter { 41cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 42cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard /** 43cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * Used for Log actions from this class 44cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard */ 45cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard private static final String TAG = BinaryDictionaryGetter.class.getSimpleName(); 46cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 4786e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard /** 4883207fb482b13bd2300008aa153080f0706fbd8dJean Chalard * Used to return empty lists 4983207fb482b13bd2300008aa153080f0706fbd8dJean Chalard */ 5083207fb482b13bd2300008aa153080f0706fbd8dJean Chalard private static final File[] EMPTY_FILE_ARRAY = new File[0]; 5183207fb482b13bd2300008aa153080f0706fbd8dJean Chalard 5283207fb482b13bd2300008aa153080f0706fbd8dJean Chalard /** 5386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * Name of the common preferences name to know which word list are on and which are off. 5486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard */ 5586e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs"; 5686e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard 570df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard // Name of the category for the main dictionary 580df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard private static final String MAIN_DICTIONARY_CATEGORY = "main"; 590df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard public static final String ID_CATEGORY_SEPARATOR = ":"; 600df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard 6113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // The key considered to read the version attribute in a dictionary file. 6213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard private static String VERSION_KEY = "version"; 6313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard 64cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard // Prevents this from being instantiated 65cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard private BinaryDictionaryGetter() {} 66cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 67cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard /** 6886e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * Returns whether we may want to use this character as part of a file name. 6986e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * 7086e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * This basically only accepts ascii letters and numbers, and rejects everything else. 7186e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard */ 7286e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard private static boolean isFileNameCharacter(int codePoint) { 7386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit 7486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase 7586e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase 7686e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard return codePoint == '_'; // Underscore 7786e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } 7886e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard 7986e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard /** 8028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * Escapes a string for any characters that may be suspicious for a file or directory name. 8128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * 8228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * Concretely this does a sort of URL-encoding except it will encode everything that's not 8328966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which 8428966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * we cannot allow here) 8528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard */ 8628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard // TODO: create a unit test for this method 8786e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard private static String replaceFileNameDangerousCharacters(final String name) { 8828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard // This assumes '%' is fully available as a non-separator, normal 8928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard // character in a file name. This is probably true for all file systems. 9028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard final StringBuilder sb = new StringBuilder(); 919242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard final int nameLength = name.length(); 929242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) { 9328966734619251f78812f6a53f5efacbf5f77c49Jean Chalard final int codePoint = name.codePointAt(i); 9486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if (isFileNameCharacter(codePoint)) { 9528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard sb.appendCodePoint(codePoint); 9628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } else { 9786e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard // 6 digits - unicode is limited to 21 bits 9886e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard sb.append(String.format((Locale)null, "%%%1$06x", codePoint)); 9986e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } 10086e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } 10186e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard return sb.toString(); 10286e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } 10386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard 10486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard /** 10586e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * Reverse escaping done by replaceFileNameDangerousCharacters. 10686e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard */ 10786e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard private static String getWordListIdFromFileName(final String fname) { 10886e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard final StringBuilder sb = new StringBuilder(); 1099242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard final int fnameLength = fname.length(); 1109242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) { 11186e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard final int codePoint = fname.codePointAt(i); 11286e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if ('%' != codePoint) { 11386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard sb.appendCodePoint(codePoint); 11486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } else { 11586e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard final int encodedCodePoint = Integer.parseInt(fname.substring(i + 1, i + 7), 16); 11686e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard i += 6; 11786e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard sb.appendCodePoint(encodedCodePoint); 11828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 11928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 12028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard return sb.toString(); 12128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 12228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard 12328966734619251f78812f6a53f5efacbf5f77c49Jean Chalard /** 124de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * Helper method to get the top level cache directory. 125de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard */ 126de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard private static String getWordListCacheDirectory(final Context context) { 127de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard return context.getFilesDir() + File.separator + "dicts"; 128de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 129de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard 130de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard /** 13128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * Find out the cache directory associated with a specific locale. 13228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard */ 133de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard private static String getCacheDirectoryForLocale(final String locale, final Context context) { 134de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale); 135de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator 13628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard + relativeDirectoryName; 13728966734619251f78812f6a53f5efacbf5f77c49Jean Chalard final File directory = new File(absoluteDirectoryName); 13828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard if (!directory.exists()) { 13928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard if (!directory.mkdirs()) { 14028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard Log.e(TAG, "Could not create the directory for locale" + locale); 14128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 14228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 14328966734619251f78812f6a53f5efacbf5f77c49Jean Chalard return absoluteDirectoryName; 14428966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 14528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard 14628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard /** 14728966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * Generates a file name for the id and locale passed as an argument. 14828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * 14928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * In the current implementation the file name returned will always be unique for 15028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * any id/locale pair, but please do not expect that the id can be the same for 15128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * different dictionaries with different locales. An id should be unique for any 15228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * dictionary. 15328966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * The file name is pretty much an URL-encoded version of the id inside a directory 15428966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * named like the locale, except it will also escape characters that look dangerous 15528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * to some file systems. 15628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * @param id the id of the dictionary for which to get a file name 157de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * @param locale the locale for which to get the file name as a string 15828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * @param context the context to use for getting the directory 15928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * @return the name of the file to be created 16028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard */ 161de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard public static String getCacheFileName(String id, String locale, Context context) { 16228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard final String fileName = replaceFileNameDangerousCharacters(id); 16328966734619251f78812f6a53f5efacbf5f77c49Jean Chalard return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; 16428966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 16528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard 16628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard /** 167cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * Returns a file address from a resource, or null if it cannot be opened. 168cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard */ 169e150ef98569d61078e0f8c67ded8364a9c3d4a20Jean Chalard private static AssetFileAddress loadFallbackResource(final Context context, 17078ab80844b4f8e0369f4e86b2a02208197f9bd34Tadashi G. Takaoka final int fallbackResId) { 17178ab80844b4f8e0369f4e86b2a02208197f9bd34Tadashi G. Takaoka final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId); 172cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard if (afd == null) { 173cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId=" 174cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard + fallbackResId); 175cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard return null; 176cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard } 177cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard return AssetFileAddress.makeFromFileNameAndOffset( 178cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength()); 179cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard } 180cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 181c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard static private class DictPackSettings { 182c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard final SharedPreferences mDictPreferences; 183c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard public DictPackSettings(final Context context) { 184c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard Context dictPackContext = null; 185c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard try { 186c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard final String dictPackName = 187c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard context.getString(R.string.dictionary_pack_package_name); 188c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard dictPackContext = context.createPackageContext(dictPackName, 0); 189c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } catch (NameNotFoundException e) { 190c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // The dictionary pack is not installed... 191c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // TODO: fallback on the built-in dict, see the TODO above 192c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard Log.e(TAG, "Could not find a dictionary pack"); 193c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 194c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard mDictPreferences = null == dictPackContext ? null 195c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard : dictPackContext.getSharedPreferences(COMMON_PREFERENCES_NAME, 196c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS); 197c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 198c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard public boolean isWordListActive(final String dictId) { 199c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard if (null == mDictPreferences) { 200c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // If we don't have preferences it basically means we can't find the dictionary 201c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // pack - either it's not installed, or it's disabled, or there is some strange 202c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // bug. Either way, a word list with no settings should be on by default: default 203c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // dictionaries in LatinIME are on if there is no settings at all, and if for some 204c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // reason some dictionaries have been installed BUT the dictionary pack can't be 205c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // found anymore it's safer to actually supply installed dictionaries. 206c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard return true; 207c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } else { 208c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // The default is true here for the same reasons as above. We got the dictionary 209c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // pack but if we don't have any settings for it it means the user has never been 210c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // to the settings yet. So by default, the main dictionaries should be on. 211c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard return mDictPreferences.getBoolean(dictId, true); 212c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 213c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 214c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 215c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard 216cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard /** 217de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * Helper method to the list of cache directories, one for each distinct locale. 218de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard */ 219de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard private static File[] getCachedDirectoryList(final Context context) { 220de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard return new File(getWordListCacheDirectory(context)).listFiles(); 221de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 222de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard 223de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard /** 2240df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * Returns the category for a given file name. 2250df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * 2260df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * This parses the file name, extracts the category, and returns it. See 2270df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}. 2280df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * @return The category as a string or null if it can't be found in the file name. 2290df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard */ 2300df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard private static String getCategoryFromFileName(final String fileName) { 2310df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final String id = getWordListIdFromFileName(fileName); 2320df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); 2330df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard if (2 != idArray.length) return null; 2340df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard return idArray[0]; 2350df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 2360df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard 2370df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard /** 2380df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * Utility class for the {@link #getCachedWordLists} method 2390df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard */ 2400df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard private static class FileAndMatchLevel { 2410df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final File mFile; 2420df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final int mMatchLevel; 2430df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard public FileAndMatchLevel(final File file, final int matchLevel) { 2440df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard mFile = file; 2450df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard mMatchLevel = matchLevel; 2460df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 2470df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 2480df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard 2490df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard /** 2500df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * Returns the list of cached files for a specific locale, one for each category. 2510df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * 2520df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * This will return exactly one file for each word list category that matches 2530df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * the passed locale. If several files match the locale for any given category, 2540df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * this returns the file with the closest match to the locale. For example, if 2550df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * the passed word list is en_US, and for a category we have an en and an en_US 2560df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * word list available, we'll return only the en_US one. 2570df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * Thus, the list will contain as many files as there are categories. 25808868624ede5eb4950972833f015d465408d3408Jean Chalard * 259de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * @param locale the locale to find the dictionary files for, as a string. 26008868624ede5eb4950972833f015d465408d3408Jean Chalard * @param context the context on which to open the files upon. 26183207fb482b13bd2300008aa153080f0706fbd8dJean Chalard * @return an array of binary dictionary files, which may be empty but may not be null. 26208868624ede5eb4950972833f015d465408d3408Jean Chalard */ 263de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard private static File[] getCachedWordLists(final String locale, 26408868624ede5eb4950972833f015d465408d3408Jean Chalard final Context context) { 265de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final File[] directoryList = getCachedDirectoryList(context); 266de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard if (null == directoryList) return EMPTY_FILE_ARRAY; 2675f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap(); 268de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard for (File directory : directoryList) { 269de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard if (!directory.isDirectory()) continue; 270de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final String dirLocale = getWordListIdFromFileName(directory.getName()); 2710df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale); 2720df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard if (LocaleUtils.isMatch(matchLevel)) { 273de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final File[] wordLists = directory.listFiles(); 274de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard if (null != wordLists) { 275de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard for (File wordList : wordLists) { 2760df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final String category = getCategoryFromFileName(wordList.getName()); 2770df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final FileAndMatchLevel currentBestMatch = cacheFiles.get(category); 2780df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard if (null == currentBestMatch || currentBestMatch.mMatchLevel < matchLevel) { 2790df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel)); 2800df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 281de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 282de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 283de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 284de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 285de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard if (cacheFiles.isEmpty()) return EMPTY_FILE_ARRAY; 2860df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final File[] result = new File[cacheFiles.size()]; 2870df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard int index = 0; 2880df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard for (final FileAndMatchLevel entry : cacheFiles.values()) { 2890df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard result[index++] = entry.mFile; 2900df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 2910df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard return result; 29208868624ede5eb4950972833f015d465408d3408Jean Chalard } 29308868624ede5eb4950972833f015d465408d3408Jean Chalard 29408868624ede5eb4950972833f015d465408d3408Jean Chalard /** 295b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * Remove all files with the passed id, except the passed file. 296b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * 297b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * If a dictionary with a given ID has a metadata change that causes it to change 298b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * path, we need to remove the old version. The only way to do this is to check all 299b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * installed files for a matching ID in a different directory. 300b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard */ 301b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard public static void removeFilesWithIdExcept(final Context context, final String id, 302b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final File fileToKeep) { 303b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard try { 304b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final File canonicalFileToKeep = fileToKeep.getCanonicalFile(); 305b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final File[] directoryList = getCachedDirectoryList(context); 306b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (null == directoryList) return; 307b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard for (File directory : directoryList) { 308b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard // There is one directory per locale. See #getCachedDirectoryList 309b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (!directory.isDirectory()) continue; 310b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final File[] wordLists = directory.listFiles(); 311b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (null == wordLists) continue; 312b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard for (File wordList : wordLists) { 313b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final String fileId = getWordListIdFromFileName(wordList.getName()); 314b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (fileId.equals(id)) { 315b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) { 316b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard wordList.delete(); 317b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 318b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 319b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 320b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 321b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } catch (java.io.IOException e) { 322b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard Log.e(TAG, "IOException trying to cleanup files : " + e); 323b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 324b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 325b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard 326b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard 327b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard /** 328de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * Returns the id associated with the main word list for a specified locale. 329de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * 330de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * Word lists stored in Android Keyboard's resources are referred to as the "main" 331de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * word lists. Since they can be updated like any other list, we need to assign a 332de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * unique ID to them. This ID is just the name of the language (locale-wise) they 333de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * are for, and this method returns this ID. 334ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard */ 335ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard private static String getMainDictId(final Locale locale) { 336de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard // This works because we don't include by default different dictionaries for 337de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard // different countries. This actually needs to return the id that we would 338de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard // like to use for word lists included in resources, and the following is okay. 3390df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard return MAIN_DICTIONARY_CATEGORY + ID_CATEGORY_SEPARATOR + locale.getLanguage().toString(); 3400df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 3410df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard 3420df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard private static boolean isMainWordListId(final String id) { 3430df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); 3440df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard if (2 != idArray.length) return false; 3450df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); 346ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard } 347ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard 34813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason 34913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // for this is, since those do not include whitelist entries, the new code with an old version 35013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // of the dictionary would lose whitelist functionality. 35113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) { 35213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // Only for English - other languages didn't have a whitelist, hence this 35313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // ad-hock ## HACK ## 35413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true; 35513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard 356d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada FileInputStream inStream = null; 35713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard try { 35813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // Read the version of the file 359d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada inStream = new FileInputStream(f); 360d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada final ByteBuffer buffer = inStream.getChannel().map( 361d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada FileChannel.MapMode.READ_ONLY, 0, f.length()); 362d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada final int magic = buffer.getInt(); 36381d97eec0e77e72cce606f9c9f96091c0b348190Yuichiro Hanada if (magic != FormatSpec.VERSION_2_MAGIC_NUMBER) { 36413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 36513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } 366d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada final int formatVersion = buffer.getInt(); 367d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada final int headerSize = buffer.getInt(); 3685f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka final HashMap<String, String> options = CollectionUtils.newHashMap(); 369d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada BinaryDictInputOutput.populateOptions(buffer, headerSize, options); 370d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada 37113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard final String version = options.get(VERSION_KEY); 37213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard if (null == version) { 37313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // No version in the options : the format is unexpected 37413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 37513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } 37613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // Version 18 is the first one to include the whitelist 37713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // Obviously this is a big ## HACK ## 37813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return Integer.parseInt(version) >= 18; 37913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } catch (java.io.FileNotFoundException e) { 38013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 38113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } catch (java.io.IOException e) { 38213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 38313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } catch (NumberFormatException e) { 38413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 385d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada } finally { 386d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada if (inStream != null) { 387d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada try { 388d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada inStream.close(); 389d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada } catch (IOException e) { 390d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada // do nothing 391d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada } 392d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada } 39313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } 39413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } 39513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard 396ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard /** 397d8f52a4f18d22aa150846b01017410ce70bbad6fJean Chalard * Returns a list of file addresses for a given locale, trying relevant methods in order. 398cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * 399d8f52a4f18d22aa150846b01017410ce70bbad6fJean Chalard * Tries to get binary dictionaries from various sources, in order: 400d8f52a4f18d22aa150846b01017410ce70bbad6fJean Chalard * - Uses a content provider to get a public dictionary set, as per the protocol described 401cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * in BinaryDictionaryFileDumper. 402cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * If that fails: 403e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard * - Gets a file name from the built-in dictionary for this locale, if any. 404cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * If that fails: 405cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * - Returns null. 406660776e09b9a3b321074a94721d901a035ca1b9fKen Wakasa * @return The list of addresses of valid dictionary files, or null. 407cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard */ 408660776e09b9a3b321074a94721d901a035ca1b9fKen Wakasa public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale, 409e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard final Context context) { 41080e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard 411cec8552b18fd74517512a43a8d75f64e64bd12c3Jean Chalard final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale); 4127b1f74bb9ddae952f4da6c8d9bbb0057984b0988Jean Chalard // cacheWordListsFromContentProvider returns the list of files it copied to local 41380e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard // storage, but we don't really care about what was copied NOW: what we want is the 41480e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard // list of everything we ever cached, so we ignore the return value. 415cec8552b18fd74517512a43a8d75f64e64bd12c3Jean Chalard BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, 416cec8552b18fd74517512a43a8d75f64e64bd12c3Jean Chalard hasDefaultWordList); 417de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); 418ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard final String mainDictId = getMainDictId(locale); 41983207fb482b13bd2300008aa153080f0706fbd8dJean Chalard final DictPackSettings dictPackSettings = new DictPackSettings(context); 42083207fb482b13bd2300008aa153080f0706fbd8dJean Chalard 421ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard boolean foundMainDict = false; 4225f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList(); 4237b1f74bb9ddae952f4da6c8d9bbb0057984b0988Jean Chalard // cachedWordLists may not be null, see doc for getCachedDictionaryList 4247b1f74bb9ddae952f4da6c8d9bbb0057984b0988Jean Chalard for (final File f : cachedWordLists) { 42583207fb482b13bd2300008aa153080f0706fbd8dJean Chalard final String wordListId = getWordListIdFromFileName(f.getName()); 42613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f); 42713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard if (canUse && isMainWordListId(wordListId)) { 428ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard foundMainDict = true; 429ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard } 43083207fb482b13bd2300008aa153080f0706fbd8dJean Chalard if (!dictPackSettings.isWordListActive(wordListId)) continue; 43113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard if (canUse) { 43283207fb482b13bd2300008aa153080f0706fbd8dJean Chalard fileList.add(AssetFileAddress.makeFromFileName(f.getPath())); 43383207fb482b13bd2300008aa153080f0706fbd8dJean Chalard } else { 43413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard Log.e(TAG, "Found a cached dictionary file but cannot read or use it"); 43583207fb482b13bd2300008aa153080f0706fbd8dJean Chalard } 43683207fb482b13bd2300008aa153080f0706fbd8dJean Chalard } 43783207fb482b13bd2300008aa153080f0706fbd8dJean Chalard 438ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) { 439e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard final int fallbackResId = 440e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard DictionaryFactory.getMainDictionaryResourceId(context.getResources(), locale); 44178ab80844b4f8e0369f4e86b2a02208197f9bd34Tadashi G. Takaoka final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId); 442ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard if (null != fallbackAsset) { 443ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard fileList.add(fallbackAsset); 444ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard } 445cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard } 44680e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard 447ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard return fileList; 448cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard } 449cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard} 450