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