BinaryDictionaryGetter.java revision 13822d2b056543de5a54b5ed338ca2cc250d8287
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; 2013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard 21cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport android.content.Context; 2286e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalardimport android.content.SharedPreferences; 2386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalardimport android.content.pm.PackageManager.NameNotFoundException; 24cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport android.content.res.AssetFileDescriptor; 25cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport android.util.Log; 26cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 2728966734619251f78812f6a53f5efacbf5f77c49Jean Chalardimport java.io.File; 2813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalardimport java.io.RandomAccessFile; 2908868624ede5eb4950972833f015d465408d3408Jean Chalardimport java.util.ArrayList; 300df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalardimport java.util.HashMap; 31cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport java.util.Locale; 32cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 33cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard/** 34cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * Helper class to get the address of a mmap'able dictionary file. 35cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard */ 36cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardclass BinaryDictionaryGetter { 37cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 38cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard /** 39cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * Used for Log actions from this class 40cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard */ 41cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard private static final String TAG = BinaryDictionaryGetter.class.getSimpleName(); 42cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 4386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard /** 4483207fb482b13bd2300008aa153080f0706fbd8dJean Chalard * Used to return empty lists 4583207fb482b13bd2300008aa153080f0706fbd8dJean Chalard */ 4683207fb482b13bd2300008aa153080f0706fbd8dJean Chalard private static final File[] EMPTY_FILE_ARRAY = new File[0]; 4783207fb482b13bd2300008aa153080f0706fbd8dJean Chalard 4883207fb482b13bd2300008aa153080f0706fbd8dJean Chalard /** 4986e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * Name of the common preferences name to know which word list are on and which are off. 5086e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard */ 5186e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs"; 5286e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard 530df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard // Name of the category for the main dictionary 540df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard private static final String MAIN_DICTIONARY_CATEGORY = "main"; 550df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard public static final String ID_CATEGORY_SEPARATOR = ":"; 560df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard 5713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // The key considered to read the version attribute in a dictionary file. 5813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard private static String VERSION_KEY = "version"; 5913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard 60cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard // Prevents this from being instantiated 61cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard private BinaryDictionaryGetter() {} 62cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 63cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard /** 6486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * Returns whether we may want to use this character as part of a file name. 6586e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * 6686e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * This basically only accepts ascii letters and numbers, and rejects everything else. 6786e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard */ 6886e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard private static boolean isFileNameCharacter(int codePoint) { 6986e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit 7086e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase 7186e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase 7286e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard return codePoint == '_'; // Underscore 7386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } 7486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard 7586e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard /** 7628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * Escapes a string for any characters that may be suspicious for a file or directory name. 7728966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * 7828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * Concretely this does a sort of URL-encoding except it will encode everything that's not 7928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which 8028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * we cannot allow here) 8128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard */ 8228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard // TODO: create a unit test for this method 8386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard private static String replaceFileNameDangerousCharacters(final String name) { 8428966734619251f78812f6a53f5efacbf5f77c49Jean Chalard // This assumes '%' is fully available as a non-separator, normal 8528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard // character in a file name. This is probably true for all file systems. 8628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard final StringBuilder sb = new StringBuilder(); 879242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard final int nameLength = name.length(); 889242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) { 8928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard final int codePoint = name.codePointAt(i); 9086e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if (isFileNameCharacter(codePoint)) { 9128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard sb.appendCodePoint(codePoint); 9228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } else { 9386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard // 6 digits - unicode is limited to 21 bits 9486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard sb.append(String.format((Locale)null, "%%%1$06x", codePoint)); 9586e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } 9686e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } 9786e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard return sb.toString(); 9886e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } 9986e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard 10086e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard /** 10186e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard * Reverse escaping done by replaceFileNameDangerousCharacters. 10286e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard */ 10386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard private static String getWordListIdFromFileName(final String fname) { 10486e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard final StringBuilder sb = new StringBuilder(); 1059242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard final int fnameLength = fname.length(); 1069242a2bcf8a6b07bb045a8356711bed1493c251eJean Chalard for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) { 10786e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard final int codePoint = fname.codePointAt(i); 10886e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard if ('%' != codePoint) { 10986e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard sb.appendCodePoint(codePoint); 11086e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard } else { 11186e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard final int encodedCodePoint = Integer.parseInt(fname.substring(i + 1, i + 7), 16); 11286e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard i += 6; 11386e517fe4a5981f6ab936a0f9f40a0e0aa196477Jean Chalard sb.appendCodePoint(encodedCodePoint); 11428966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 11528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 11628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard return sb.toString(); 11728966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 11828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard 11928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard /** 120de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * Helper method to get the top level cache directory. 121de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard */ 122de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard private static String getWordListCacheDirectory(final Context context) { 123de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard return context.getFilesDir() + File.separator + "dicts"; 124de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 125de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard 126de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard /** 12728966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * Find out the cache directory associated with a specific locale. 12828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard */ 129de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard private static String getCacheDirectoryForLocale(final String locale, final Context context) { 130de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale); 131de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator 13228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard + relativeDirectoryName; 13328966734619251f78812f6a53f5efacbf5f77c49Jean Chalard final File directory = new File(absoluteDirectoryName); 13428966734619251f78812f6a53f5efacbf5f77c49Jean Chalard if (!directory.exists()) { 13528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard if (!directory.mkdirs()) { 13628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard Log.e(TAG, "Could not create the directory for locale" + locale); 13728966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 13828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 13928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard return absoluteDirectoryName; 14028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 14128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard 14228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard /** 14328966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * Generates a file name for the id and locale passed as an argument. 14428966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * 14528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * In the current implementation the file name returned will always be unique for 14628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * any id/locale pair, but please do not expect that the id can be the same for 14728966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * different dictionaries with different locales. An id should be unique for any 14828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * dictionary. 14928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * The file name is pretty much an URL-encoded version of the id inside a directory 15028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * named like the locale, except it will also escape characters that look dangerous 15128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * to some file systems. 15228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * @param id the id of the dictionary for which to get a file name 153de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * @param locale the locale for which to get the file name as a string 15428966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * @param context the context to use for getting the directory 15528966734619251f78812f6a53f5efacbf5f77c49Jean Chalard * @return the name of the file to be created 15628966734619251f78812f6a53f5efacbf5f77c49Jean Chalard */ 157de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard public static String getCacheFileName(String id, String locale, Context context) { 15828966734619251f78812f6a53f5efacbf5f77c49Jean Chalard final String fileName = replaceFileNameDangerousCharacters(id); 15928966734619251f78812f6a53f5efacbf5f77c49Jean Chalard return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; 16028966734619251f78812f6a53f5efacbf5f77c49Jean Chalard } 16128966734619251f78812f6a53f5efacbf5f77c49Jean Chalard 16228966734619251f78812f6a53f5efacbf5f77c49Jean Chalard /** 163cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * Returns a file address from a resource, or null if it cannot be opened. 164cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard */ 165e150ef98569d61078e0f8c67ded8364a9c3d4a20Jean Chalard private static AssetFileAddress loadFallbackResource(final Context context, 16678ab80844b4f8e0369f4e86b2a02208197f9bd34Tadashi G. Takaoka final int fallbackResId) { 16778ab80844b4f8e0369f4e86b2a02208197f9bd34Tadashi G. Takaoka final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId); 168cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard if (afd == null) { 169cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId=" 170cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard + fallbackResId); 171cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard return null; 172cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard } 173cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard return AssetFileAddress.makeFromFileNameAndOffset( 174cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength()); 175cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard } 176cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 177c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard static private class DictPackSettings { 178c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard final SharedPreferences mDictPreferences; 179c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard public DictPackSettings(final Context context) { 180c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard Context dictPackContext = null; 181c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard try { 182c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard final String dictPackName = 183c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard context.getString(R.string.dictionary_pack_package_name); 184c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard dictPackContext = context.createPackageContext(dictPackName, 0); 185c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } catch (NameNotFoundException e) { 186c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // The dictionary pack is not installed... 187c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // TODO: fallback on the built-in dict, see the TODO above 188c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard Log.e(TAG, "Could not find a dictionary pack"); 189c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 190c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard mDictPreferences = null == dictPackContext ? null 191c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard : dictPackContext.getSharedPreferences(COMMON_PREFERENCES_NAME, 192c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS); 193c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 194c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard public boolean isWordListActive(final String dictId) { 195c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard if (null == mDictPreferences) { 196c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // If we don't have preferences it basically means we can't find the dictionary 197c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // pack - either it's not installed, or it's disabled, or there is some strange 198c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // bug. Either way, a word list with no settings should be on by default: default 199c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // dictionaries in LatinIME are on if there is no settings at all, and if for some 200c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // reason some dictionaries have been installed BUT the dictionary pack can't be 201c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // found anymore it's safer to actually supply installed dictionaries. 202c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard return true; 203c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } else { 204c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // The default is true here for the same reasons as above. We got the dictionary 205c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // pack but if we don't have any settings for it it means the user has never been 206c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard // to the settings yet. So by default, the main dictionaries should be on. 207c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard return mDictPreferences.getBoolean(dictId, true); 208c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 209c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 210c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard } 211c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard 212cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard /** 213de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * Helper method to the list of cache directories, one for each distinct locale. 214de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard */ 215de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard private static File[] getCachedDirectoryList(final Context context) { 216de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard return new File(getWordListCacheDirectory(context)).listFiles(); 217de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 218de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard 219de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard /** 2200df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * Returns the category for a given file name. 2210df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * 2220df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * This parses the file name, extracts the category, and returns it. See 2230df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}. 2240df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * @return The category as a string or null if it can't be found in the file name. 2250df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard */ 2260df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard private static String getCategoryFromFileName(final String fileName) { 2270df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final String id = getWordListIdFromFileName(fileName); 2280df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); 2290df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard if (2 != idArray.length) return null; 2300df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard return idArray[0]; 2310df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 2320df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard 2330df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard /** 2340df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * Utility class for the {@link #getCachedWordLists} method 2350df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard */ 2360df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard private static class FileAndMatchLevel { 2370df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final File mFile; 2380df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final int mMatchLevel; 2390df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard public FileAndMatchLevel(final File file, final int matchLevel) { 2400df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard mFile = file; 2410df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard mMatchLevel = matchLevel; 2420df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 2430df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 2440df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard 2450df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard /** 2460df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * Returns the list of cached files for a specific locale, one for each category. 2470df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * 2480df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * This will return exactly one file for each word list category that matches 2490df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * the passed locale. If several files match the locale for any given category, 2500df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * this returns the file with the closest match to the locale. For example, if 2510df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * the passed word list is en_US, and for a category we have an en and an en_US 2520df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * word list available, we'll return only the en_US one. 2530df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard * Thus, the list will contain as many files as there are categories. 25408868624ede5eb4950972833f015d465408d3408Jean Chalard * 255de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * @param locale the locale to find the dictionary files for, as a string. 25608868624ede5eb4950972833f015d465408d3408Jean Chalard * @param context the context on which to open the files upon. 25783207fb482b13bd2300008aa153080f0706fbd8dJean Chalard * @return an array of binary dictionary files, which may be empty but may not be null. 25808868624ede5eb4950972833f015d465408d3408Jean Chalard */ 259de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard private static File[] getCachedWordLists(final String locale, 26008868624ede5eb4950972833f015d465408d3408Jean Chalard final Context context) { 261de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final File[] directoryList = getCachedDirectoryList(context); 262de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard if (null == directoryList) return EMPTY_FILE_ARRAY; 2630df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final HashMap<String, FileAndMatchLevel> cacheFiles = 2640df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard new HashMap<String, FileAndMatchLevel>(); 265de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard for (File directory : directoryList) { 266de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard if (!directory.isDirectory()) continue; 267de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final String dirLocale = getWordListIdFromFileName(directory.getName()); 2680df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale); 2690df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard if (LocaleUtils.isMatch(matchLevel)) { 270de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final File[] wordLists = directory.listFiles(); 271de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard if (null != wordLists) { 272de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard for (File wordList : wordLists) { 2730df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final String category = getCategoryFromFileName(wordList.getName()); 2740df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final FileAndMatchLevel currentBestMatch = cacheFiles.get(category); 2750df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard if (null == currentBestMatch || currentBestMatch.mMatchLevel < matchLevel) { 2760df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel)); 2770df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 278de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 279de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 280de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 281de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard } 282de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard if (cacheFiles.isEmpty()) return EMPTY_FILE_ARRAY; 2830df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final File[] result = new File[cacheFiles.size()]; 2840df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard int index = 0; 2850df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard for (final FileAndMatchLevel entry : cacheFiles.values()) { 2860df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard result[index++] = entry.mFile; 2870df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 2880df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard return result; 28908868624ede5eb4950972833f015d465408d3408Jean Chalard } 29008868624ede5eb4950972833f015d465408d3408Jean Chalard 29108868624ede5eb4950972833f015d465408d3408Jean Chalard /** 292b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * Remove all files with the passed id, except the passed file. 293b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * 294b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * If a dictionary with a given ID has a metadata change that causes it to change 295b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * path, we need to remove the old version. The only way to do this is to check all 296b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard * installed files for a matching ID in a different directory. 297b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard */ 298b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard public static void removeFilesWithIdExcept(final Context context, final String id, 299b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final File fileToKeep) { 300b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard try { 301b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final File canonicalFileToKeep = fileToKeep.getCanonicalFile(); 302b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final File[] directoryList = getCachedDirectoryList(context); 303b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (null == directoryList) return; 304b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard for (File directory : directoryList) { 305b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard // There is one directory per locale. See #getCachedDirectoryList 306b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (!directory.isDirectory()) continue; 307b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final File[] wordLists = directory.listFiles(); 308b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (null == wordLists) continue; 309b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard for (File wordList : wordLists) { 310b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard final String fileId = getWordListIdFromFileName(wordList.getName()); 311b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (fileId.equals(id)) { 312b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) { 313b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard wordList.delete(); 314b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 315b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 316b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 317b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 318b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } catch (java.io.IOException e) { 319b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard Log.e(TAG, "IOException trying to cleanup files : " + e); 320b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 321b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard } 322b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard 323b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard 324b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard /** 325de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * Returns the id associated with the main word list for a specified locale. 326de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * 327de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * Word lists stored in Android Keyboard's resources are referred to as the "main" 328de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * word lists. Since they can be updated like any other list, we need to assign a 329de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * unique ID to them. This ID is just the name of the language (locale-wise) they 330de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard * are for, and this method returns this ID. 331ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard */ 332ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard private static String getMainDictId(final Locale locale) { 333de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard // This works because we don't include by default different dictionaries for 334de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard // different countries. This actually needs to return the id that we would 335de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard // like to use for word lists included in resources, and the following is okay. 3360df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard return MAIN_DICTIONARY_CATEGORY + ID_CATEGORY_SEPARATOR + locale.getLanguage().toString(); 3370df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard } 3380df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard 3390df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard private static boolean isMainWordListId(final String id) { 3400df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); 3410df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard if (2 != idArray.length) return false; 3420df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); 343ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard } 344ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard 34513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason 34613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // for this is, since those do not include whitelist entries, the new code with an old version 34713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // of the dictionary would lose whitelist functionality. 34813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) { 34913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // Only for English - other languages didn't have a whitelist, hence this 35013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // ad-hock ## HACK ## 35113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true; 35213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard 35313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard try { 35413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // Read the version of the file 35513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard final RandomAccessFile raf = new RandomAccessFile(f, "r"); 35613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard final int magic = raf.readInt(); 35713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard if (magic != BinaryDictInputOutput.VERSION_2_MAGIC_NUMBER) { 35813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 35913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } 36013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard final int formatVersion = raf.readInt(); 36113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard final int headerSize = raf.readInt(); 36213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard final HashMap<String, String> options = new HashMap<String, String>(); 36313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard BinaryDictInputOutput.populateOptionsFromFile(raf, headerSize, options); 36413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard final String version = options.get(VERSION_KEY); 36513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard if (null == version) { 36613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // No version in the options : the format is unexpected 36713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 36813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } 36913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // Version 18 is the first one to include the whitelist 37013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard // Obviously this is a big ## HACK ## 37113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return Integer.parseInt(version) >= 18; 37213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } catch (java.io.FileNotFoundException e) { 37313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 37413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } catch (java.io.IOException e) { 37513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 37613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } catch (NumberFormatException e) { 37713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard return false; 37813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } 37913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard } 38013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard 381ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard /** 382d8f52a4f18d22aa150846b01017410ce70bbad6fJean Chalard * Returns a list of file addresses for a given locale, trying relevant methods in order. 383cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * 384d8f52a4f18d22aa150846b01017410ce70bbad6fJean Chalard * Tries to get binary dictionaries from various sources, in order: 385d8f52a4f18d22aa150846b01017410ce70bbad6fJean Chalard * - Uses a content provider to get a public dictionary set, as per the protocol described 386cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * in BinaryDictionaryFileDumper. 387cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * If that fails: 388e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard * - Gets a file name from the built-in dictionary for this locale, if any. 389cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * If that fails: 390cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard * - Returns null. 391660776e09b9a3b321074a94721d901a035ca1b9fKen Wakasa * @return The list of addresses of valid dictionary files, or null. 392cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard */ 393660776e09b9a3b321074a94721d901a035ca1b9fKen Wakasa public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale, 394e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard final Context context) { 39580e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard 396cec8552b18fd74517512a43a8d75f64e64bd12c3Jean Chalard final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale); 3977b1f74bb9ddae952f4da6c8d9bbb0057984b0988Jean Chalard // cacheWordListsFromContentProvider returns the list of files it copied to local 39880e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard // storage, but we don't really care about what was copied NOW: what we want is the 39980e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard // list of everything we ever cached, so we ignore the return value. 400cec8552b18fd74517512a43a8d75f64e64bd12c3Jean Chalard BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, 401cec8552b18fd74517512a43a8d75f64e64bd12c3Jean Chalard hasDefaultWordList); 402de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); 403ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard final String mainDictId = getMainDictId(locale); 40483207fb482b13bd2300008aa153080f0706fbd8dJean Chalard final DictPackSettings dictPackSettings = new DictPackSettings(context); 40583207fb482b13bd2300008aa153080f0706fbd8dJean Chalard 406ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard boolean foundMainDict = false; 40783207fb482b13bd2300008aa153080f0706fbd8dJean Chalard final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>(); 4087b1f74bb9ddae952f4da6c8d9bbb0057984b0988Jean Chalard // cachedWordLists may not be null, see doc for getCachedDictionaryList 4097b1f74bb9ddae952f4da6c8d9bbb0057984b0988Jean Chalard for (final File f : cachedWordLists) { 41083207fb482b13bd2300008aa153080f0706fbd8dJean Chalard final String wordListId = getWordListIdFromFileName(f.getName()); 41113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f); 41213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard if (canUse && isMainWordListId(wordListId)) { 413ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard foundMainDict = true; 414ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard } 41583207fb482b13bd2300008aa153080f0706fbd8dJean Chalard if (!dictPackSettings.isWordListActive(wordListId)) continue; 41613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard if (canUse) { 41783207fb482b13bd2300008aa153080f0706fbd8dJean Chalard fileList.add(AssetFileAddress.makeFromFileName(f.getPath())); 41883207fb482b13bd2300008aa153080f0706fbd8dJean Chalard } else { 41913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard Log.e(TAG, "Found a cached dictionary file but cannot read or use it"); 42083207fb482b13bd2300008aa153080f0706fbd8dJean Chalard } 42183207fb482b13bd2300008aa153080f0706fbd8dJean Chalard } 42283207fb482b13bd2300008aa153080f0706fbd8dJean Chalard 423ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) { 424e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard final int fallbackResId = 425e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard DictionaryFactory.getMainDictionaryResourceId(context.getResources(), locale); 42678ab80844b4f8e0369f4e86b2a02208197f9bd34Tadashi G. Takaoka final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId); 427ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard if (null != fallbackAsset) { 428ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard fileList.add(fallbackAsset); 429ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard } 430cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard } 43180e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard 432ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard return fileList; 433cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard } 434cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard} 435