diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java | 210 |
1 files changed, 37 insertions, 173 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index c747dc673..e913f2852 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -1,21 +1,22 @@ /* * Copyright (C) 2011 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; import com.android.inputmethod.latin.makedict.FormatSpec; @@ -55,7 +56,7 @@ final class BinaryDictionaryGetter { private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs"; // Name of the category for the main dictionary - private static final String MAIN_DICTIONARY_CATEGORY = "main"; + public static final String MAIN_DICTIONARY_CATEGORY = "main"; public static final String ID_CATEGORY_SEPARATOR = ":"; // The key considered to read the version attribute in a dictionary file. @@ -65,120 +66,21 @@ final class BinaryDictionaryGetter { private BinaryDictionaryGetter() {} /** - * Returns whether we may want to use this character as part of a file name. - * - * This basically only accepts ascii letters and numbers, and rejects everything else. - */ - private static boolean isFileNameCharacter(int codePoint) { - if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit - if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase - if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase - return codePoint == '_'; // Underscore - } - - /** - * Escapes a string for any characters that may be suspicious for a file or directory name. - * - * Concretely this does a sort of URL-encoding except it will encode everything that's not - * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which - * we cannot allow here) - */ - // TODO: create a unit test for this method - private static String replaceFileNameDangerousCharacters(final String name) { - // This assumes '%' is fully available as a non-separator, normal - // character in a file name. This is probably true for all file systems. - final StringBuilder sb = new StringBuilder(); - final int nameLength = name.length(); - for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) { - final int codePoint = name.codePointAt(i); - if (isFileNameCharacter(codePoint)) { - sb.appendCodePoint(codePoint); - } else { - // 6 digits - unicode is limited to 21 bits - sb.append(String.format((Locale)null, "%%%1$06x", codePoint)); - } - } - return sb.toString(); - } - - /** - * Reverse escaping done by replaceFileNameDangerousCharacters. - */ - private static String getWordListIdFromFileName(final String fname) { - final StringBuilder sb = new StringBuilder(); - final int fnameLength = fname.length(); - for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) { - final int codePoint = fname.codePointAt(i); - if ('%' != codePoint) { - sb.appendCodePoint(codePoint); - } else { - final int encodedCodePoint = Integer.parseInt(fname.substring(i + 1, i + 7), 16); - i += 6; - sb.appendCodePoint(encodedCodePoint); - } - } - return sb.toString(); - } - - /** - * Helper method to get the top level cache directory. - */ - private static String getWordListCacheDirectory(final Context context) { - return context.getFilesDir() + File.separator + "dicts"; - } - - /** - * Find out the cache directory associated with a specific locale. - */ - private static String getCacheDirectoryForLocale(final String locale, final Context context) { - final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale); - final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator - + relativeDirectoryName; - final File directory = new File(absoluteDirectoryName); - if (!directory.exists()) { - if (!directory.mkdirs()) { - Log.e(TAG, "Could not create the directory for locale" + locale); - } - } - return absoluteDirectoryName; - } - - /** - * Generates a file name for the id and locale passed as an argument. - * - * In the current implementation the file name returned will always be unique for - * any id/locale pair, but please do not expect that the id can be the same for - * different dictionaries with different locales. An id should be unique for any - * dictionary. - * The file name is pretty much an URL-encoded version of the id inside a directory - * named like the locale, except it will also escape characters that look dangerous - * to some file systems. - * @param id the id of the dictionary for which to get a file name - * @param locale the locale for which to get the file name as a string - * @param context the context to use for getting the directory - * @return the name of the file to be created - */ - public static String getCacheFileName(String id, String locale, Context context) { - final String fileName = replaceFileNameDangerousCharacters(id); - return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; - } - - /** * Generates a unique temporary file name in the app cache directory. - * - * This is unique as long as it doesn't get called twice in the same millisecond by the same - * thread, which should be more than enough for our purposes. */ - public static String getTempFileName(String id, Context context) { - final String fileName = replaceFileNameDangerousCharacters(id); - return context.getCacheDir() + File.separator + fileName + "." - + Thread.currentThread().getId() + "." + System.currentTimeMillis(); + public static String getTempFileName(final String id, final Context context) + throws IOException { + final String safeId = DictionaryInfoUtils.replaceFileNameDangerousCharacters(id); + // If the first argument is less than three chars, createTempFile throws a + // RuntimeException. We don't really care about what name we get, so just + // put a three-chars prefix makes us safe. + return File.createTempFile("xxx" + safeId, null).getAbsolutePath(); } /** * Returns a file address from a resource, or null if it cannot be opened. */ - private static AssetFileAddress loadFallbackResource(final Context context, + public static AssetFileAddress loadFallbackResource(final Context context, final int fallbackResId) { final AssetFileDescriptor afd = context.getResources().openRawResourceFd(fallbackResId); if (afd == null) { @@ -226,27 +128,6 @@ final class BinaryDictionaryGetter { } /** - * Helper method to the list of cache directories, one for each distinct locale. - */ - private static File[] getCachedDirectoryList(final Context context) { - return new File(getWordListCacheDirectory(context)).listFiles(); - } - - /** - * Returns the category for a given file name. - * - * This parses the file name, extracts the category, and returns it. See - * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}. - * @return The category as a string or null if it can't be found in the file name. - */ - private static String getCategoryFromFileName(final String fileName) { - final String id = getWordListIdFromFileName(fileName); - final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); - if (2 != idArray.length) return null; - return idArray[0]; - } - - /** * Utility class for the {@link #getCachedWordLists} method */ private static final class FileAndMatchLevel { @@ -272,20 +153,21 @@ final class BinaryDictionaryGetter { * @param context the context on which to open the files upon. * @return an array of binary dictionary files, which may be empty but may not be null. */ - private static File[] getCachedWordLists(final String locale, - final Context context) { - final File[] directoryList = getCachedDirectoryList(context); + public static File[] getCachedWordLists(final String locale, final Context context) { + final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context); if (null == directoryList) return EMPTY_FILE_ARRAY; final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap(); for (File directory : directoryList) { if (!directory.isDirectory()) continue; - final String dirLocale = getWordListIdFromFileName(directory.getName()); + final String dirLocale = + DictionaryInfoUtils.getWordListIdFromFileName(directory.getName()); final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale); if (LocaleUtils.isMatch(matchLevel)) { final File[] wordLists = directory.listFiles(); if (null != wordLists) { for (File wordList : wordLists) { - final String category = getCategoryFromFileName(wordList.getName()); + final String category = + DictionaryInfoUtils.getCategoryFromFileName(wordList.getName()); final FileAndMatchLevel currentBestMatch = cacheFiles.get(category); if (null == currentBestMatch || currentBestMatch.mMatchLevel < matchLevel) { cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel)); @@ -314,7 +196,7 @@ final class BinaryDictionaryGetter { final File fileToKeep) { try { final File canonicalFileToKeep = fileToKeep.getCanonicalFile(); - final File[] directoryList = getCachedDirectoryList(context); + final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context); if (null == directoryList) return; for (File directory : directoryList) { // There is one directory per locale. See #getCachedDirectoryList @@ -322,7 +204,8 @@ final class BinaryDictionaryGetter { final File[] wordLists = directory.listFiles(); if (null == wordLists) continue; for (File wordList : wordLists) { - final String fileId = getWordListIdFromFileName(wordList.getName()); + final String fileId = + DictionaryInfoUtils.getWordListIdFromFileName(wordList.getName()); if (fileId.equals(id)) { if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) { wordList.delete(); @@ -331,32 +214,10 @@ final class BinaryDictionaryGetter { } } } catch (java.io.IOException e) { - Log.e(TAG, "IOException trying to cleanup files : " + e); + Log.e(TAG, "IOException trying to cleanup files", e); } } - - /** - * Returns the id associated with the main word list for a specified locale. - * - * Word lists stored in Android Keyboard's resources are referred to as the "main" - * word lists. Since they can be updated like any other list, we need to assign a - * unique ID to them. This ID is just the name of the language (locale-wise) they - * are for, and this method returns this ID. - */ - private static String getMainDictId(final Locale locale) { - // This works because we don't include by default different dictionaries for - // different countries. This actually needs to return the id that we would - // like to use for word lists included in resources, and the following is okay. - return MAIN_DICTIONARY_CATEGORY + ID_CATEGORY_SEPARATOR + locale.getLanguage().toString(); - } - - private static boolean isMainWordListId(final String id) { - final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); - if (2 != idArray.length) return false; - return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); - } - // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason // for this is, since those do not include whitelist entries, the new code with an old version // of the dictionary would lose whitelist functionality. @@ -427,19 +288,22 @@ final class BinaryDictionaryGetter { // cacheWordListsFromContentProvider returns the list of files it copied to local // storage, but we don't really care about what was copied NOW: what we want is the // list of everything we ever cached, so we ignore the return value. - BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, - hasDefaultWordList); + // TODO: The experimental version is not supported by the Dictionary Pack Service yet + if (!ProductionFlag.IS_EXPERIMENTAL) { + BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, + hasDefaultWordList); + } final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); - final String mainDictId = getMainDictId(locale); + final String mainDictId = DictionaryInfoUtils.getMainDictId(locale); final DictPackSettings dictPackSettings = new DictPackSettings(context); boolean foundMainDict = false; final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList(); // cachedWordLists may not be null, see doc for getCachedDictionaryList for (final File f : cachedWordLists) { - final String wordListId = getWordListIdFromFileName(f.getName()); + final String wordListId = DictionaryInfoUtils.getWordListIdFromFileName(f.getName()); final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f); - if (canUse && isMainWordListId(wordListId)) { + if (canUse && DictionaryInfoUtils.isMainWordListId(wordListId)) { foundMainDict = true; } if (!dictPackSettings.isWordListActive(wordListId)) continue; @@ -452,7 +316,7 @@ final class BinaryDictionaryGetter { if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) { final int fallbackResId = - DictionaryFactory.getMainDictionaryResourceId(context.getResources(), locale); + DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale); final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId); if (null != fallbackAsset) { fileList.add(fallbackAsset); |