diff options
Diffstat (limited to 'java/src')
3 files changed, 169 insertions, 56 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index ed5f83b3b..24bb7b78a 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -63,10 +63,10 @@ public class BinaryDictionaryFileDumper { } /** - * Queries a content provider for the list of dictionaries for a specific locale + * Queries a content provider for the list of word lists for a specific locale * available to copy into Latin IME. */ - private static List<String> getDictIdList(final Locale locale, final Context context) { + private static List<String> getWordListIds(final Locale locale, final Context context) { final ContentResolver resolver = context.getContentResolver(); final Uri dictionaryPackUri = getProviderUri(locale.toString()); @@ -87,77 +87,150 @@ public class BinaryDictionaryFileDumper { return list; } + /** - * Queries a content provider for dictionary data for some locale and cache the returned files - * - * This will query a content provider for dictionary data for a given locale, and copy the - * files locally so that they can be mmap'ed. This may overwrite previously cached dictionaries - * with newer versions if a newer version is made available by the content provider. - * @returns the addresses of the files, or null if no data could be obtained. - * @throw FileNotFoundException if the provider returns non-existent data. - * @throw IOException if the provider-returned data could not be read. + * Helper method to encapsulate exception handling. */ - public static List<AssetFileAddress> cacheDictionariesFromContentProvider(final Locale locale, - final Context context) { - final ContentResolver resolver = context.getContentResolver(); - final List<String> idList = getDictIdList(locale, context); - final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>(); - for (String id : idList) { - final Uri wordListUri = getProviderUri(id); + private static AssetFileDescriptor openAssetFileDescriptor(final ContentResolver resolver, + final Uri uri) { + try { + return resolver.openAssetFileDescriptor(uri, "r"); + } catch (FileNotFoundException e) { + // I don't want to log the word list URI here for security concerns + Log.e(TAG, "Could not find a word list from the dictionary provider."); + return null; + } + } + + /** + * Caches a word list the id of which is passed as an argument. This will write the file + * to the cache file name designated by its id and locale, overwriting it if already present + * and creating it (and its containing directory) if necessary. + */ + private static AssetFileAddress cacheWordList(final String id, final Locale locale, + final ContentResolver resolver, final Context context) { + + final int COMPRESSED_CRYPTED_COMPRESSED = 0; + final int CRYPTED_COMPRESSED = 1; + final int COMPRESSED_CRYPTED = 2; + final int COMPRESSED_ONLY = 3; + final int CRYPTED_ONLY = 4; + final int NONE = 5; + final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED; + final int MODE_MAX = NONE; + + final Uri wordListUri = getProviderUri(id); + final String outputFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context); + + for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) { + InputStream originalSourceStream = null; + InputStream inputStream = null; + FileOutputStream outputStream = null; AssetFileDescriptor afd = null; try { - afd = resolver.openAssetFileDescriptor(wordListUri, "r"); - } catch (FileNotFoundException e) { - // leave null inside afd and continue - } - if (null == afd) continue; - try { - final String fileName = copyFileTo(afd.createInputStream(), - BinaryDictionaryGetter.getCacheFileName(id, locale, context)); - afd.close(); + // Open input. + afd = openAssetFileDescriptor(resolver, wordListUri); + // If we can't open it at all, don't even try a number of times. + if (null == afd) return null; + originalSourceStream = afd.createInputStream(); + // Open output. + outputStream = new FileOutputStream(outputFileName); + // Get the appropriate decryption method for this try + switch (mode) { + case COMPRESSED_CRYPTED_COMPRESSED: + inputStream = FileTransforms.getUncompressedStream( + FileTransforms.getDecryptedStream( + FileTransforms.getUncompressedStream( + originalSourceStream))); + break; + case CRYPTED_COMPRESSED: + inputStream = FileTransforms.getUncompressedStream( + FileTransforms.getDecryptedStream(originalSourceStream)); + break; + case COMPRESSED_CRYPTED: + inputStream = FileTransforms.getDecryptedStream( + FileTransforms.getUncompressedStream(originalSourceStream)); + break; + case COMPRESSED_ONLY: + inputStream = FileTransforms.getUncompressedStream(originalSourceStream); + break; + case CRYPTED_ONLY: + inputStream = FileTransforms.getDecryptedStream(originalSourceStream); + break; + case NONE: + inputStream = originalSourceStream; + break; + } + copyFileTo(inputStream, outputStream); if (0 >= resolver.delete(wordListUri, null, null)) { - // I'd rather not print the word list ID to the log out of security concerns Log.e(TAG, "Could not have the dictionary pack delete a word list"); } - fileAddressList.add(AssetFileAddress.makeFromFileName(fileName)); - } catch (IOException e) { - // Can't read the file for some reason. Continue onto the next file. - Log.e(TAG, "Cannot read a word list from the dictionary pack : " + e); + // Success! Close files (through the finally{} clause) and return. + return AssetFileAddress.makeFromFileName(outputFileName); + } catch (Exception e) { + Log.e(TAG, "Can't open word list in mode " + mode + " : " + e); + // Try the next method. + } finally { + // Ignore exceptions while closing files. + try { + // afd.close() will close inputStream, we should not call inputStream.close(). + if (null != afd) afd.close(); + } catch (Exception e) { + Log.e(TAG, "Exception while closing a cross-process file descriptor : " + e); + } + try { + if (null != outputStream) outputStream.close(); + } catch (Exception e) { + Log.e(TAG, "Exception while closing a file : " + e); + } } } - return fileAddressList; + + // We could not copy the file at all. This is very unexpected. + // I'd rather not print the word list ID to the log out of security concerns + Log.e(TAG, "Could not copy a word list. Will not be able to use it."); + // If we can't copy it we should probably delete it to avoid trying to copy it over + // and over each time we open LatinIME. + if (0 >= resolver.delete(wordListUri, null, null)) { + Log.e(TAG, "In addition, we were unable to delete it."); + } + return null; } /** - * Accepts a resource number as dictionary data for some locale and returns the name of a file. + * Queries a content provider for word list data for some locale and cache the returned files * - * This will make the resource the cached dictionary for this locale, overwriting any previous - * cached data. + * This will query a content provider for word list data for a given locale, and copy the + * files locally so that they can be mmap'ed. This may overwrite previously cached word lists + * with newer versions if a newer version is made available by the content provider. + * @returns the addresses of the word list files, or null if no data could be obtained. + * @throw FileNotFoundException if the provider returns non-existent data. + * @throw IOException if the provider-returned data could not be read. */ - public static String getDictionaryFileFromResource(int resource, Locale locale, - Context context) throws FileNotFoundException, IOException { - final Resources res = context.getResources(); - final Locale savedLocale = Utils.setSystemLocale(res, locale); - final InputStream stream = res.openRawResource(resource); - Utils.setSystemLocale(res, savedLocale); - return copyFileTo(stream, - BinaryDictionaryGetter.getCacheFileName(Integer.toString(resource), - locale, context)); + public static List<AssetFileAddress> cacheWordListsFromContentProvider(final Locale locale, + final Context context) { + final ContentResolver resolver = context.getContentResolver(); + final List<String> idList = getWordListIds(locale, context); + final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>(); + for (String id : idList) { + final AssetFileAddress afd = cacheWordList(id, locale, resolver, context); + if (null != afd) { + fileAddressList.add(afd); + } + } + return fileAddressList; } /** - * Copies the data in an input stream to a target file, creating the file if necessary and - * overwriting it if it already exists. + * Copies the data in an input stream to a target file. * @param input the stream to be copied. - * @param outputFileName the name of a file to copy the data to. It is created if necessary. + * @param outputFile an outputstream to copy the data to. */ - private static String copyFileTo(final InputStream input, final String outputFileName) + private static void copyFileTo(final InputStream input, final FileOutputStream output) throws FileNotFoundException, IOException { final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE]; - final FileOutputStream output = new FileOutputStream(outputFileName); for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) output.write(buffer, 0, readBytes); input.close(); - return outputFileName; } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 5d2dab0a9..38344300c 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -205,7 +205,7 @@ 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[] getCachedDictionaryList(final Locale locale, + private static File[] getCachedWordLists(final Locale locale, final Context context) { final String directoryName = getCacheDirectoryForLocale(locale, context); final File[] cacheFiles = new File(directoryName).listFiles(); @@ -235,11 +235,11 @@ class BinaryDictionaryGetter { public static List<AssetFileAddress> getDictionaryFiles(final Locale locale, final Context context, final int fallbackResId) { - // cacheDictionariesFromContentProvider returns the list of files it copied to local + // 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.cacheDictionariesFromContentProvider(locale, context); - final File[] cachedDictionaryList = getCachedDictionaryList(locale, context); + BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context); + final File[] cachedWordLists = getCachedWordLists(locale, context); final String mainDictId = getMainDictId(locale); @@ -247,8 +247,8 @@ class BinaryDictionaryGetter { boolean foundMainDict = false; final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>(); - // cachedDictionaryList may not be null, see doc for getCachedDictionaryList - for (final File f : cachedDictionaryList) { + // cachedWordLists may not be null, see doc for getCachedDictionaryList + for (final File f : cachedWordLists) { final String wordListId = getWordListIdFromFileName(f.getName()); if (wordListId.equals(mainDictId)) { foundMainDict = true; diff --git a/java/src/com/android/inputmethod/latin/FileTransforms.java b/java/src/com/android/inputmethod/latin/FileTransforms.java new file mode 100644 index 000000000..d0374e01e --- /dev/null +++ b/java/src/com/android/inputmethod/latin/FileTransforms.java @@ -0,0 +1,40 @@ +/* + * 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 + * + * 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. + */ + +package com.android.inputmethod.latin; + +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPInputStream; + +public class FileTransforms { + public static OutputStream getCryptedStream(OutputStream out) { + // Crypt the stream. + return out; + } + + public static InputStream getDecryptedStream(InputStream in) { + // Decrypt the stream. + return in; + } + + public static InputStream getUncompressedStream(InputStream in) throws IOException { + return new GZIPInputStream(in); + } +} |