diff options
author | 2012-07-04 15:38:14 +0900 | |
---|---|---|
committer | 2012-07-04 15:38:21 +0900 | |
commit | c68b37964b083015967ce290991ad69d29a4055d (patch) | |
tree | 909bddc0f0c699b6344015e7ff67e31c0a1f4eff /java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java | |
parent | 1e094ac19b99c47f0b8a5108e20949ac91cfa03e (diff) | |
parent | 30a324a58dbe1e2dc47d83c1bcc0af262ab0d542 (diff) | |
download | latinime-c68b37964b083015967ce290991ad69d29a4055d.tar.gz latinime-c68b37964b083015967ce290991ad69d29a4055d.tar.xz latinime-c68b37964b083015967ce290991ad69d29a4055d.zip |
Merge remote-tracking branch 'goog/master' into mergescript
Conflicts:
CleanSpec.mk
java/Android.mk
java/res/drawable-large-hdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-large-hdpi/hint_popup_holo.9.png
java/res/drawable-large-hdpi/sym_keyboard_numsymbol_holo.png
java/res/drawable-large-hdpi/sym_keyboard_tab_holo.png
java/res/drawable-large-land-hdpi/hint_popup_holo.9.png
java/res/drawable-large-land-mdpi/hint_popup_holo.9.png
java/res/drawable-large-land-xhdpi/hint_popup_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_normal_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_normal_off_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_normal_on_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_pressed_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_light_normal_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_light_pressed_holo.9.png
java/res/drawable-large-mdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-large-mdpi/hint_popup_holo.9.png
java/res/drawable-large-mdpi/keyboard_background_holo.9.png
java/res/drawable-large-mdpi/keyboard_popup_panel_background_holo.9.png
java/res/drawable-large-mdpi/keyboard_suggest_strip_holo.9.png
java/res/drawable-large-mdpi/sym_keyboard_delete_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num0_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num1_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num2_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num3_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num4_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num5_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num6_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num7_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num8_holo.png
java/res/drawable-large-mdpi/sym_keyboard_num9_holo.png
java/res/drawable-large-mdpi/sym_keyboard_numbpound_holo.png
java/res/drawable-large-mdpi/sym_keyboard_numbstar_holo.png
java/res/drawable-large-mdpi/sym_keyboard_numsymbol_holo.png
java/res/drawable-large-mdpi/sym_keyboard_return_holo.png
java/res/drawable-large-mdpi/sym_keyboard_settings_holo.png
java/res/drawable-large-mdpi/sym_keyboard_shift_holo.png
java/res/drawable-large-mdpi/sym_keyboard_shift_locked_holo.png
java/res/drawable-large-mdpi/sym_keyboard_space_holo.png
java/res/drawable-large-mdpi/sym_keyboard_tab_holo.png
java/res/drawable-large-mdpi/sym_keyboard_voice_holo.png
java/res/drawable-large-mdpi/sym_keyboard_voice_off_holo.png
java/res/drawable-large-xhdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-large-xhdpi/hint_popup_holo.9.png
java/res/drawable-large-xhdpi/sym_keyboard_numsymbol_holo.png
java/res/drawable-large-xhdpi/sym_keyboard_tab_holo.png
java/res/drawable-xlarge-hdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-xlarge-hdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-land-hdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-land-mdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-land-xhdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_normal_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_normal_off_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_normal_on_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_pressed_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_light_normal_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_light_pressed_holo.9.png
java/res/drawable-xlarge-mdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-xlarge-mdpi/hint_popup_holo.9.png
java/res/drawable-xlarge-mdpi/keyboard_background_holo.9.png
java/res/drawable-xlarge-mdpi/keyboard_popup_panel_background_holo.9.png
java/res/drawable-xlarge-mdpi/keyboard_suggest_strip_holo.9.png
java/res/drawable-xlarge-mdpi/sym_keyboard_delete_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num0_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num1_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num2_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num3_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num4_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num5_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num6_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num7_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num8_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_num9_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_numbpound_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_numbstar_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_return_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_settings_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_shift_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_shift_locked_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_space_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_voice_holo.png
java/res/drawable-xlarge-mdpi/sym_keyboard_voice_off_holo.png
java/res/drawable-xlarge-xhdpi/btn_keyboard_key_popup_selected_holo.9.png
java/res/drawable-xlarge-xhdpi/hint_popup_holo.9.png
java/res/layout-xlarge/recognition_status.xml
java/res/values-af/strings.xml
java/res/values-am/strings.xml
java/res/values-ar/strings.xml
java/res/values-be/strings.xml
java/res/values-bg/strings.xml
java/res/values-ca/strings.xml
java/res/values-cs/strings.xml
java/res/values-da/strings.xml
java/res/values-de/strings.xml
java/res/values-el/strings.xml
java/res/values-en-rGB/strings.xml
java/res/values-es-rUS/strings.xml
java/res/values-es/strings.xml
java/res/values-et/strings.xml
java/res/values-fa/strings.xml
java/res/values-fi/strings.xml
java/res/values-fr/strings.xml
java/res/values-hi/strings.xml
java/res/values-hr/strings.xml
java/res/values-hu/strings.xml
java/res/values-in/strings.xml
java/res/values-it/strings.xml
java/res/values-iw/strings.xml
java/res/values-ja/strings.xml
java/res/values-ko/strings.xml
java/res/values-large/donottranslate.xml
java/res/values-lt/strings.xml
java/res/values-lv/strings.xml
java/res/values-ms/strings.xml
java/res/values-nb/strings.xml
java/res/values-nl/strings.xml
java/res/values-pl/strings.xml
java/res/values-pt-rPT/strings.xml
java/res/values-pt/strings.xml
java/res/values-rm/strings.xml
java/res/values-ro/strings.xml
java/res/values-ru/strings.xml
java/res/values-sk/strings.xml
java/res/values-sl/strings.xml
java/res/values-sr/strings.xml
java/res/values-sv/strings.xml
java/res/values-sw/strings.xml
java/res/values-sw600dp/donottranslate.xml
java/res/values-sw768dp/donottranslate.xml
java/res/values-th/strings.xml
java/res/values-tl/strings.xml
java/res/values-tr/strings.xml
java/res/values-uk/strings.xml
java/res/values-vi/strings.xml
java/res/values-xlarge/donottranslate.xml
java/res/values-zh-rCN/strings.xml
java/res/values-zh-rTW/strings.xml
java/res/values-zu/strings.xml
java/res/values/keypress-vibration-durations.xml
java/res/values/predefined-subtypes.xml
java/res/xml-large-land/kbd_popup_template.xml
java/res/xml-large/kbd_key_styles.xml
java/res/xml-large/kbd_popup_template.xml
java/res/xml-large/kbd_qwerty_f2.xml
java/res/xml-large/kbd_qwerty_row1.xml
java/res/xml-large/kbd_qwerty_row2.xml
java/res/xml-large/kbd_qwerty_row3.xml
java/res/xml-large/kbd_qwerty_row4.xml
java/res/xml-large/kbd_row3_right.xml
java/res/xml-large/kbd_rows_arabic.xml
java/res/xml-large/kbd_rows_azerty.xml
java/res/xml-large/kbd_rows_hebrew.xml
java/res/xml-large/kbd_rows_qwerty.xml
java/res/xml-large/kbd_rows_qwertz.xml
java/res/xml-large/kbd_rows_russian.xml
java/res/xml-large/kbd_rows_scandinavian.xml
java/res/xml-large/kbd_rows_serbian.xml
java/res/xml-large/kbd_rows_spanish.xml
java/res/xml-large/kbd_symbols.xml
java/res/xml-large/kbd_symbols_shift.xml
java/res/xml-sw600dp-land/kbd_more_keys_keyboard_template.xml
java/res/xml-sw600dp-land/kbd_popup_template.xml
java/res/xml-sw600dp/kbd_more_keys_keyboard_template.xml
java/res/xml-sw600dp/kbd_popup_template.xml
java/res/xml-sw600dp/kbd_row3_right.xml
java/res/xml-sw600dp/kbd_rows_qwerty.xml
java/res/xml-sw600dp/keys_comma_period.xml
java/res/xml-sw768dp-land/kbd_more_keys_keyboard_template.xml
java/res/xml-sw768dp-land/kbd_popup_template.xml
java/res/xml-sw768dp/kbd_more_keys_keyboard_template.xml
java/res/xml-sw768dp/kbd_popup_template.xml
java/res/xml-sw768dp/kbd_row3_right2.xml
java/res/xml-sw768dp/kbd_rows_qwerty.xml
java/res/xml-sw768dp/row_symbols_shift4.xml
java/res/xml-xlarge-land/kbd_popup_template.xml
java/res/xml-xlarge/kbd_key_styles.xml
java/res/xml-xlarge/kbd_popup_template.xml
java/res/xml-xlarge/kbd_qwerty_row1.xml
java/res/xml-xlarge/kbd_qwerty_row2.xml
java/res/xml-xlarge/kbd_qwerty_row3.xml
java/res/xml-xlarge/kbd_qwerty_row4.xml
java/res/xml-xlarge/kbd_row3_right2.xml
java/res/xml-xlarge/kbd_rows_arabic.xml
java/res/xml-xlarge/kbd_rows_azerty.xml
java/res/xml-xlarge/kbd_rows_hebrew.xml
java/res/xml-xlarge/kbd_rows_qwerty.xml
java/res/xml-xlarge/kbd_rows_qwertz.xml
java/res/xml-xlarge/kbd_rows_russian.xml
java/res/xml-xlarge/kbd_rows_scandinavian.xml
java/res/xml-xlarge/kbd_rows_serbian.xml
java/res/xml-xlarge/kbd_rows_spanish.xml
java/res/xml-xlarge/kbd_symbols.xml
java/res/xml-xlarge/kbd_symbols_shift.xml
java/res/xml/key_azerty_quote.xml
java/res/xml/key_f1.xml
java/res/xml/method.xml
java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
java/src/com/android/inputmethod/latin/Utils.java
native/Android.mk
Change-Id: I96e8e042f636ed8e5cc023cf8514f13121e39195
Diffstat (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java | 301 |
1 files changed, 226 insertions, 75 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 76a230f82..37eced5d6 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -19,16 +19,20 @@ package com.android.inputmethod.latin; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; +import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; +import android.util.Log; +import java.io.BufferedInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -37,115 +41,262 @@ import java.util.Locale; * file from the dictionary provider */ public class BinaryDictionaryFileDumper { + private static final String TAG = BinaryDictionaryFileDumper.class.getSimpleName(); + private static final boolean DEBUG = false; + /** * The size of the temporary buffer to copy files. */ - static final int FILE_READ_BUFFER_SIZE = 1024; + private static final int FILE_READ_BUFFER_SIZE = 1024; + // TODO: make the following data common with the native code + private static final byte[] MAGIC_NUMBER_VERSION_1 = + new byte[] { (byte)0x78, (byte)0xB1, (byte)0x00, (byte)0x00 }; + private static final byte[] MAGIC_NUMBER_VERSION_2 = + new byte[] { (byte)0x9B, (byte)0xC1, (byte)0x3A, (byte)0xFE }; + + private static final String DICTIONARY_PROJECTION[] = { "id" }; + + public static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt"; + public static final String QUERY_PARAMETER_TRUE = "true"; + public static final String QUERY_PARAMETER_DELETE_RESULT = "result"; + public static final String QUERY_PARAMETER_SUCCESS = "success"; + public static final String QUERY_PARAMETER_FAILURE = "failure"; // Prevents this class to be accidentally instantiated. private BinaryDictionaryFileDumper() { } /** - * Generates a file name that matches the locale passed as an argument. - * The file name is basically the result of the .toString() method, except we replace - * any @File.separator with an underscore to avoid generating a file name that may not - * be created. - * @param locale the locale for which to get the file name - * @param context the context to use for getting the directory - * @return the name of the file to be created + * Returns a URI builder pointing to the dictionary pack. + * + * This creates a URI builder able to build a URI pointing to the dictionary + * pack content provider for a specific dictionary id. */ - private static String getCacheFileNameForLocale(Locale locale, Context context) { - // The following assumes two things : - // 1. That File.separator is not the same character as "_" - // I don't think any android system will ever use "_" as a path separator - // 2. That no two locales differ by only a File.separator versus a "_" - // Since "_" can't be part of locale components this should be safe. - // Examples: - // en -> en - // en_US_POSIX -> en_US_POSIX - // en__foo/bar -> en__foo_bar - final String[] separator = { File.separator }; - final String[] empty = { "_" }; - final CharSequence basename = TextUtils.replace(locale.toString(), separator, empty); - return context.getFilesDir() + File.separator + basename; + private static Uri.Builder getProviderUriBuilder(final String path) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath( + path); } /** - * Return for a given locale the provider URI to query to get the dictionary. + * Queries a content provider for the list of word lists for a specific locale + * available to copy into Latin IME. */ - public static Uri getProviderUri(Locale locale) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath( - locale.toString()).build(); + private static List<WordListInfo> getWordListWordListInfos(final Locale locale, + final Context context, final boolean hasDefaultWordList) { + final ContentResolver resolver = context.getContentResolver(); + final Uri.Builder builder = getProviderUriBuilder(locale.toString()); + if (!hasDefaultWordList) { + builder.appendQueryParameter(QUERY_PARAMETER_MAY_PROMPT_USER, QUERY_PARAMETER_TRUE); + } + final Uri dictionaryPackUri = builder.build(); + + final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null, null); + if (null == c) return Collections.<WordListInfo>emptyList(); + if (c.getCount() <= 0 || !c.moveToFirst()) { + c.close(); + return Collections.<WordListInfo>emptyList(); + } + + try { + final List<WordListInfo> list = new ArrayList<WordListInfo>(); + do { + final String wordListId = c.getString(0); + final String wordListLocale = c.getString(1); + if (TextUtils.isEmpty(wordListId)) continue; + list.add(new WordListInfo(wordListId, wordListLocale)); + } while (c.moveToNext()); + c.close(); + return list; + } catch (Exception e) { + // Just in case we hit a problem in communication with the dictionary pack. + // We don't want to die. + Log.e(TAG, "Exception communicating with the dictionary pack : " + e); + return Collections.<WordListInfo>emptyList(); + } } + /** - * Queries a content provider for dictionary data for some locale and returns the file addresses - * - * This will query a content provider for dictionary data for a given locale, and return - * the addresses of a file set the members of which are suitable to be mmap'ed. It will copy - * them to local storage if needed. - * It should also check the dictionary versions to avoid unnecessary copies but this is - * still in TODO state. - * This will make the data from the content provider the cached dictionary for this locale, - * overwriting any previous cached data. - * @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> getDictSetFromContentProvider(Locale locale, - Context context) throws FileNotFoundException, IOException { - // TODO: check whether the dictionary is the same or not and if it is, return the cached - // file. - // TODO: This should be able to read a number of files from the dictionary pack, copy - // them all and return them. - final ContentResolver resolver = context.getContentResolver(); - final Uri dictionaryPackUri = getProviderUri(locale); - final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(dictionaryPackUri, "r"); - if (null == afd) return null; - final String fileName = - copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context)); - return Arrays.asList(AssetFileAddress.makeFromFileName(fileName)); + 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; + } } /** - * Accepts a file as dictionary data for some locale and returns the name of a file. - * - * This will make the data in the input file the cached dictionary for this locale, overwriting - * any previous cached data. + * 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. */ - public static String getDictionaryFileFromFile(String fileName, Locale locale, - Context context) throws FileNotFoundException, IOException { - return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale, - context)); + private static AssetFileAddress cacheWordList(final String id, final String 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.Builder wordListUriBuilder = getProviderUriBuilder(id); + final String outputFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context); + + for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) { + InputStream originalSourceStream = null; + InputStream inputStream = null; + File outputFile = null; + FileOutputStream outputStream = null; + AssetFileDescriptor afd = null; + final Uri wordListUri = wordListUriBuilder.build(); + try { + // 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. + outputFile = new File(outputFileName); + outputStream = new FileOutputStream(outputFile); + // 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; + } + checkMagicAndCopyFileTo(new BufferedInputStream(inputStream), outputStream); + wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT, + QUERY_PARAMETER_SUCCESS); + if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) { + Log.e(TAG, "Could not have the dictionary pack delete a word list"); + } + BinaryDictionaryGetter.removeFilesWithIdExcept(context, id, outputFile); + // Success! Close files (through the finally{} clause) and return. + return AssetFileAddress.makeFromFileName(outputFileName); + } catch (Exception e) { + if (DEBUG) { + Log.i(TAG, "Can't open word list in mode " + mode + " : " + e); + } + if (null != outputFile) { + // This may or may not fail. The file may not have been created if the + // exception was thrown before it could be. Hence, both failure and + // success are expected outcomes, so we don't check the return value. + outputFile.delete(); + } + // Try the next method. + } finally { + // Ignore exceptions while closing files. + try { + // inputStream.close() will close afd, we should not call afd.close(). + if (null != inputStream) inputStream.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); + } + } + } + + // 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 warn the dictionary provider so that it can mark it + // as invalid. + wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT, + QUERY_PARAMETER_FAILURE); + if (0 >= resolver.delete(wordListUriBuilder.build(), 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 { - return copyFileTo(context.getResources().openRawResource(resource), - getCacheFileNameForLocale(locale, context)); + public static List<AssetFileAddress> cacheWordListsFromContentProvider(final Locale locale, + final Context context, final boolean hasDefaultWordList) { + final ContentResolver resolver = context.getContentResolver(); + final List<WordListInfo> idList = getWordListWordListInfos(locale, context, + hasDefaultWordList); + final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>(); + for (WordListInfo id : idList) { + final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, 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 if the magic number matches. + * + * If the magic number does not match the expected value, this method throws an + * IOException. Other usual conditions for IOException or FileNotFoundException + * also apply. + * * @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 output an output stream to copy the data to. */ - private static String copyFileTo(final InputStream input, final String outputFileName) - throws FileNotFoundException, IOException { + private static void checkMagicAndCopyFileTo(final BufferedInputStream input, + final FileOutputStream output) throws FileNotFoundException, IOException { + // Check the magic number + final int length = MAGIC_NUMBER_VERSION_2.length; + final byte[] magicNumberBuffer = new byte[length]; + final int readMagicNumberSize = input.read(magicNumberBuffer, 0, length); + if (readMagicNumberSize < length) { + throw new IOException("Less bytes to read than the magic number length"); + } + if (!Arrays.equals(MAGIC_NUMBER_VERSION_2, magicNumberBuffer)) { + if (!Arrays.equals(MAGIC_NUMBER_VERSION_1, magicNumberBuffer)) { + throw new IOException("Wrong magic number for downloaded file"); + } + } + output.write(magicNumberBuffer); + + // Actually copy the file 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; } } |