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