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;
3165e4970026fd0e7964796c0e252b6d71632b634cYuichiro Hanadaimport java.nio.BufferUnderflowException;
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 */
40a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokafinal class 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    /**
16766c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard     * Generates a unique temporary file name in the app cache directory.
16866c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard     *
16966c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard     * This is unique as long as it doesn't get called twice in the same millisecond by the same
17066c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard     * thread, which should be more than enough for our purposes.
17166c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard     */
17266c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard    public static String getTempFileName(String id, Context context) {
17366c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard        final String fileName = replaceFileNameDangerousCharacters(id);
17466c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard        return context.getCacheDir() + File.separator + fileName + "."
17566c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard                + Thread.currentThread().getId() + "." + System.currentTimeMillis();
17666c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard    }
17766c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard
17866c90cd2ae49c49da8aeda5ab1d86bd9b76434c7Jean Chalard    /**
179cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard     * Returns a file address from a resource, or null if it cannot be opened.
180cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard     */
181e150ef98569d61078e0f8c67ded8364a9c3d4a20Jean Chalard    private static AssetFileAddress loadFallbackResource(final Context context,
18278ab80844b4f8e0369f4e86b2a02208197f9bd34Tadashi G. Takaoka            final int fallbackResId) {
18378ab80844b4f8e0369f4e86b2a02208197f9bd34Tadashi G. Takaoka        final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId);
184cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard        if (afd == null) {
185cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard            Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId="
186cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard                    + fallbackResId);
187cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard            return null;
188cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard        }
189cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard        return AssetFileAddress.makeFromFileNameAndOffset(
190cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard                context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength());
191cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard    }
192cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard
193a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka    private static final class DictPackSettings {
194c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard        final SharedPreferences mDictPreferences;
195c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard        public DictPackSettings(final Context context) {
196c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard            Context dictPackContext = null;
197c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard            try {
198c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                final String dictPackName =
199c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                        context.getString(R.string.dictionary_pack_package_name);
200c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                dictPackContext = context.createPackageContext(dictPackName, 0);
201c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard            } catch (NameNotFoundException e) {
202c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // The dictionary pack is not installed...
203c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // TODO: fallback on the built-in dict, see the TODO above
204c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                Log.e(TAG, "Could not find a dictionary pack");
205c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard            }
206c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard            mDictPreferences = null == dictPackContext ? null
207c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                    : dictPackContext.getSharedPreferences(COMMON_PREFERENCES_NAME,
208c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                            Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS);
209c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard        }
210c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard        public boolean isWordListActive(final String dictId) {
211c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard            if (null == mDictPreferences) {
212c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // If we don't have preferences it basically means we can't find the dictionary
213c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // pack - either it's not installed, or it's disabled, or there is some strange
214c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // bug. Either way, a word list with no settings should be on by default: default
215c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // dictionaries in LatinIME are on if there is no settings at all, and if for some
216c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // reason some dictionaries have been installed BUT the dictionary pack can't be
217c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // found anymore it's safer to actually supply installed dictionaries.
218c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                return true;
219c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard            } else {
220c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // The default is true here for the same reasons as above. We got the dictionary
221c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // pack but if we don't have any settings for it it means the user has never been
222c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                // to the settings yet. So by default, the main dictionaries should be on.
223c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard                return mDictPreferences.getBoolean(dictId, true);
224c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard            }
225c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard        }
226c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard    }
227c11c4fd61b3574f3647299ec0f19ee01ecaabf52Jean Chalard
228cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard    /**
229de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard     * Helper method to the list of cache directories, one for each distinct locale.
230de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard     */
231de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard    private static File[] getCachedDirectoryList(final Context context) {
232de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        return new File(getWordListCacheDirectory(context)).listFiles();
233de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard    }
234de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard
235de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard    /**
2360df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * Returns the category for a given file name.
2370df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     *
2380df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * This parses the file name, extracts the category, and returns it. See
2390df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}.
2400df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * @return The category as a string or null if it can't be found in the file name.
2410df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     */
2420df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard    private static String getCategoryFromFileName(final String fileName) {
2430df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        final String id = getWordListIdFromFileName(fileName);
2440df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        final String[] idArray = id.split(ID_CATEGORY_SEPARATOR);
2450df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        if (2 != idArray.length) return null;
2460df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        return idArray[0];
2470df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard    }
2480df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard
2490df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard    /**
2500df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * Utility class for the {@link #getCachedWordLists} method
2510df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     */
252a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka    private static final class FileAndMatchLevel {
2530df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        final File mFile;
2540df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        final int mMatchLevel;
2550df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        public FileAndMatchLevel(final File file, final int matchLevel) {
2560df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard            mFile = file;
2570df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard            mMatchLevel = matchLevel;
2580df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        }
2590df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard    }
2600df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard
2610df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard    /**
2620df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * Returns the list of cached files for a specific locale, one for each category.
2630df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     *
2640df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * This will return exactly one file for each word list category that matches
2650df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * the passed locale. If several files match the locale for any given category,
2660df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * this returns the file with the closest match to the locale. For example, if
2670df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * the passed word list is en_US, and for a category we have an en and an en_US
2680df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * word list available, we'll return only the en_US one.
2690df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard     * Thus, the list will contain as many files as there are categories.
27008868624ede5eb4950972833f015d465408d3408Jean Chalard     *
271de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard     * @param locale the locale to find the dictionary files for, as a string.
27208868624ede5eb4950972833f015d465408d3408Jean Chalard     * @param context the context on which to open the files upon.
27383207fb482b13bd2300008aa153080f0706fbd8dJean Chalard     * @return an array of binary dictionary files, which may be empty but may not be null.
27408868624ede5eb4950972833f015d465408d3408Jean Chalard     */
275de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard    private static File[] getCachedWordLists(final String locale,
27608868624ede5eb4950972833f015d465408d3408Jean Chalard            final Context context) {
277de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        final File[] directoryList = getCachedDirectoryList(context);
278de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        if (null == directoryList) return EMPTY_FILE_ARRAY;
2795f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka        final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
280de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        for (File directory : directoryList) {
281de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard            if (!directory.isDirectory()) continue;
282de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard            final String dirLocale = getWordListIdFromFileName(directory.getName());
2830df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard            final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale);
2840df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard            if (LocaleUtils.isMatch(matchLevel)) {
285de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard                final File[] wordLists = directory.listFiles();
286de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard                if (null != wordLists) {
287de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard                    for (File wordList : wordLists) {
2880df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard                        final String category = getCategoryFromFileName(wordList.getName());
2890df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard                        final FileAndMatchLevel currentBestMatch = cacheFiles.get(category);
2900df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard                        if (null == currentBestMatch || currentBestMatch.mMatchLevel < matchLevel) {
2910df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard                            cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel));
2920df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard                        }
293de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard                    }
294de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard                }
295de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard            }
296de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        }
297de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        if (cacheFiles.isEmpty()) return EMPTY_FILE_ARRAY;
2980df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        final File[] result = new File[cacheFiles.size()];
2990df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        int index = 0;
3000df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        for (final FileAndMatchLevel entry : cacheFiles.values()) {
3010df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard            result[index++] = entry.mFile;
3020df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        }
3030df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        return result;
30408868624ede5eb4950972833f015d465408d3408Jean Chalard    }
30508868624ede5eb4950972833f015d465408d3408Jean Chalard
30608868624ede5eb4950972833f015d465408d3408Jean Chalard    /**
307b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard     * Remove all files with the passed id, except the passed file.
308b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard     *
309b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard     * If a dictionary with a given ID has a metadata change that causes it to change
310b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard     * path, we need to remove the old version. The only way to do this is to check all
311b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard     * installed files for a matching ID in a different directory.
312b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard     */
313b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard    public static void removeFilesWithIdExcept(final Context context, final String id,
314b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard            final File fileToKeep) {
315b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard        try {
316b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard            final File canonicalFileToKeep = fileToKeep.getCanonicalFile();
317b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard            final File[] directoryList = getCachedDirectoryList(context);
318b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard            if (null == directoryList) return;
319b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard            for (File directory : directoryList) {
320b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                // There is one directory per locale. See #getCachedDirectoryList
321b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                if (!directory.isDirectory()) continue;
322b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                final File[] wordLists = directory.listFiles();
323b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                if (null == wordLists) continue;
324b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                for (File wordList : wordLists) {
325b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                    final String fileId = getWordListIdFromFileName(wordList.getName());
326b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                    if (fileId.equals(id)) {
327b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                        if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) {
328b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                            wordList.delete();
329b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                        }
330b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                    }
331b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard                }
332b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard            }
333b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard        } catch (java.io.IOException e) {
334b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard            Log.e(TAG, "IOException trying to cleanup files : " + e);
335b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard        }
336b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard    }
337b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard
338b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard
339b9e2bce95e955b6393c25226ab62fa44d24b904aJean Chalard    /**
340de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard     * Returns the id associated with the main word list for a specified locale.
341de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard     *
342de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard     * Word lists stored in Android Keyboard's resources are referred to as the "main"
343de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard     * word lists. Since they can be updated like any other list, we need to assign a
344de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard     * unique ID to them. This ID is just the name of the language (locale-wise) they
345de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard     * are for, and this method returns this ID.
346ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard     */
347ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard    private static String getMainDictId(final Locale locale) {
348de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        // This works because we don't include by default different dictionaries for
349de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        // different countries. This actually needs to return the id that we would
350de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        // like to use for word lists included in resources, and the following is okay.
3510df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        return MAIN_DICTIONARY_CATEGORY + ID_CATEGORY_SEPARATOR + locale.getLanguage().toString();
3520df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard    }
3530df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard
3540df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard    private static boolean isMainWordListId(final String id) {
3550df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        final String[] idArray = id.split(ID_CATEGORY_SEPARATOR);
3560df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        if (2 != idArray.length) return false;
3570df78d46da1ef0d42196f3baa9d5f6df5932afb6Jean Chalard        return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
358ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard    }
359ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard
36013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard    // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
36113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard    // for this is, since those do not include whitelist entries, the new code with an old version
36213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard    // of the dictionary would lose whitelist functionality.
36313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard    private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
36413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard        // Only for English - other languages didn't have a whitelist, hence this
36599b84b42f9517cbf7856aec93a6d5de30daaa325Jean Chalard        // ad-hoc ## HACK ##
36613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard        if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
36713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard
368d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada        FileInputStream inStream = null;
36913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard        try {
37013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            // Read the version of the file
371d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada            inStream = new FileInputStream(f);
3728adc0154e6a1412e8b2a7ba5b2a67650062e0dbfYuichiro Hanada            final BinaryDictInputOutput.ByteBufferWrapper buffer =
3738adc0154e6a1412e8b2a7ba5b2a67650062e0dbfYuichiro Hanada                    new BinaryDictInputOutput.ByteBufferWrapper(inStream.getChannel().map(
3748adc0154e6a1412e8b2a7ba5b2a67650062e0dbfYuichiro Hanada                            FileChannel.MapMode.READ_ONLY, 0, f.length()));
3758adc0154e6a1412e8b2a7ba5b2a67650062e0dbfYuichiro Hanada            final int magic = buffer.readInt();
37681d97eec0e77e72cce606f9c9f96091c0b348190Yuichiro Hanada            if (magic != FormatSpec.VERSION_2_MAGIC_NUMBER) {
37713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard                return false;
37813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            }
3798adc0154e6a1412e8b2a7ba5b2a67650062e0dbfYuichiro Hanada            final int formatVersion = buffer.readInt();
3808adc0154e6a1412e8b2a7ba5b2a67650062e0dbfYuichiro Hanada            final int headerSize = buffer.readInt();
3815f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka            final HashMap<String, String> options = CollectionUtils.newHashMap();
382d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada            BinaryDictInputOutput.populateOptions(buffer, headerSize, options);
383d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada
38413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            final String version = options.get(VERSION_KEY);
38513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            if (null == version) {
38613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard                // No version in the options : the format is unexpected
38713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard                return false;
38813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            }
38913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            // Version 18 is the first one to include the whitelist
39013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            // Obviously this is a big ## HACK ##
39113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            return Integer.parseInt(version) >= 18;
39213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard        } catch (java.io.FileNotFoundException e) {
39313822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            return false;
39413822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard        } catch (java.io.IOException e) {
39513822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            return false;
39613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard        } catch (NumberFormatException e) {
39713822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            return false;
39865e4970026fd0e7964796c0e252b6d71632b634cYuichiro Hanada        } catch (BufferUnderflowException e) {
39965e4970026fd0e7964796c0e252b6d71632b634cYuichiro Hanada            return false;
400d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada        } finally {
401d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada            if (inStream != null) {
402d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada                try {
403d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada                    inStream.close();
404d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada                } catch (IOException e) {
405d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada                    // do nothing
406d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada                }
407d4fe7fda303ff937d2e44c15dde9d90cbf59376bYuichiro Hanada            }
40813822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard        }
40913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard    }
41013822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard
411ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard    /**
412d8f52a4f18d22aa150846b01017410ce70bbad6fJean Chalard     * Returns a list of file addresses for a given locale, trying relevant methods in order.
413cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard     *
414d8f52a4f18d22aa150846b01017410ce70bbad6fJean Chalard     * Tries to get binary dictionaries from various sources, in order:
415d8f52a4f18d22aa150846b01017410ce70bbad6fJean Chalard     * - Uses a content provider to get a public dictionary set, as per the protocol described
416cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard     *   in BinaryDictionaryFileDumper.
417cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard     * If that fails:
418e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard     * - Gets a file name from the built-in dictionary for this locale, if any.
419cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard     * If that fails:
420cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard     * - Returns null.
421660776e09b9a3b321074a94721d901a035ca1b9fKen Wakasa     * @return The list of addresses of valid dictionary files, or null.
422cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard     */
423660776e09b9a3b321074a94721d901a035ca1b9fKen Wakasa    public static ArrayList<AssetFileAddress> getDictionaryFiles(final Locale locale,
424e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard            final Context context) {
42580e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard
426cec8552b18fd74517512a43a8d75f64e64bd12c3Jean Chalard        final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale);
4277b1f74bb9ddae952f4da6c8d9bbb0057984b0988Jean Chalard        // cacheWordListsFromContentProvider returns the list of files it copied to local
42880e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard        // storage, but we don't really care about what was copied NOW: what we want is the
42980e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard        // list of everything we ever cached, so we ignore the return value.
430cec8552b18fd74517512a43a8d75f64e64bd12c3Jean Chalard        BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
431cec8552b18fd74517512a43a8d75f64e64bd12c3Jean Chalard                hasDefaultWordList);
432de4e8dedccc7b6db6df4c3f75d9f2458432c558aJean Chalard        final File[] cachedWordLists = getCachedWordLists(locale.toString(), context);
433ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard        final String mainDictId = getMainDictId(locale);
43483207fb482b13bd2300008aa153080f0706fbd8dJean Chalard        final DictPackSettings dictPackSettings = new DictPackSettings(context);
43583207fb482b13bd2300008aa153080f0706fbd8dJean Chalard
436ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard        boolean foundMainDict = false;
4375f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka        final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
4387b1f74bb9ddae952f4da6c8d9bbb0057984b0988Jean Chalard        // cachedWordLists may not be null, see doc for getCachedDictionaryList
4397b1f74bb9ddae952f4da6c8d9bbb0057984b0988Jean Chalard        for (final File f : cachedWordLists) {
44083207fb482b13bd2300008aa153080f0706fbd8dJean Chalard            final String wordListId = getWordListIdFromFileName(f.getName());
44113822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
44213822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            if (canUse && isMainWordListId(wordListId)) {
443ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard                foundMainDict = true;
444ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard            }
44583207fb482b13bd2300008aa153080f0706fbd8dJean Chalard            if (!dictPackSettings.isWordListActive(wordListId)) continue;
44613822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard            if (canUse) {
44783207fb482b13bd2300008aa153080f0706fbd8dJean Chalard                fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
44883207fb482b13bd2300008aa153080f0706fbd8dJean Chalard            } else {
44913822d2b056543de5a54b5ed338ca2cc250d8287Jean Chalard                Log.e(TAG, "Found a cached dictionary file but cannot read or use it");
45083207fb482b13bd2300008aa153080f0706fbd8dJean Chalard            }
45183207fb482b13bd2300008aa153080f0706fbd8dJean Chalard        }
45283207fb482b13bd2300008aa153080f0706fbd8dJean Chalard
453ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard        if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) {
454e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard            final int fallbackResId =
455e6269759d642eac0a03ae6942acb5cd556e7ff46Jean Chalard                    DictionaryFactory.getMainDictionaryResourceId(context.getResources(), locale);
45678ab80844b4f8e0369f4e86b2a02208197f9bd34Tadashi G. Takaoka            final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId);
457ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard            if (null != fallbackAsset) {
458ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard                fileList.add(fallbackAsset);
459ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard            }
460cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard        }
46180e0bf04292867ddc769aca75ebaee817b95a941Jean Chalard
462ee7daefd972979898d91974ea0d92fcc9f3ca169Jean Chalard        return fileList;
463cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard    }
464cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard}
465