From e150ef98569d61078e0f8c67ded8364a9c3d4a20 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Thu, 21 Jul 2011 17:36:57 +0900 Subject: Set the locale for opening an asset This is necessary because we don't know any more whether the locale of the process is the expected one when the dictionary is loaded asynchronously. Bug: 5023141 Change-Id: Ia9e4741f3b4a04a9f085f5b65ec122471b0c2dff --- .../com/android/inputmethod/latin/BinaryDictionaryFileDumper.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 76a230f82..00d80f566 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; +import android.content.res.Resources; import android.net.Uri; import android.text.TextUtils; @@ -129,8 +130,11 @@ public class BinaryDictionaryFileDumper { */ public static String getDictionaryFileFromResource(int resource, Locale locale, Context context) throws FileNotFoundException, IOException { - return copyFileTo(context.getResources().openRawResource(resource), - getCacheFileNameForLocale(locale, context)); + 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, getCacheFileNameForLocale(locale, context)); } /** -- cgit v1.2.3-83-g751a From d3da6e631111aaaa66d5988beafb7e95e7656c2d Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Tue, 26 Jul 2011 22:13:34 +0900 Subject: Close a multiprocess file handle. Bug: 5034192 Change-Id: I9c3d757cfae313e9b7510e167de797a74c74a351 --- java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java | 1 + 1 file changed, 1 insertion(+) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 00d80f566..41b577cf3 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -107,6 +107,7 @@ public class BinaryDictionaryFileDumper { if (null == afd) return null; final String fileName = copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context)); + afd.close(); return Arrays.asList(AssetFileAddress.makeFromFileName(fileName)); } -- cgit v1.2.3-83-g751a From fae8d60ee926e9f340392789119cf81655ad46e9 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Tue, 2 Aug 2011 19:13:44 +0900 Subject: Change the dictionary file passing schema to a list of ids The dictionary filename used to be passed directly to Latin IME. This change implements, on the part of Latin IME, the passing of them as an id that should then be passed through openAssetFileDescriptor. Bug: 5095140 Change-Id: I7d1e9d57c19f0645045368f68681680f238189fc --- .../latin/BinaryDictionaryFileDumper.java | 66 ++++++++++++++++++---- .../inputmethod/latin/BinaryDictionaryGetter.java | 43 ++++++-------- 2 files changed, 72 insertions(+), 37 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 41b577cf3..1c7599442 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -20,8 +20,10 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; +import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; +import android.util.Log; import java.io.File; import java.io.FileInputStream; @@ -29,7 +31,8 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -43,6 +46,8 @@ public class BinaryDictionaryFileDumper { */ static final int FILE_READ_BUFFER_SIZE = 1024; + private static final String DICTIONARY_PROJECTION[] = { "id" }; + // Prevents this class to be accidentally instantiated. private BinaryDictionaryFileDumper() { } @@ -75,12 +80,47 @@ public class BinaryDictionaryFileDumper { /** * Return for a given locale the provider URI to query to get the dictionary. */ + // TODO: remove this public static Uri getProviderUri(Locale locale) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath( locale.toString()).build(); } + /** + * Return for a given locale or dictionary id the provider URI to get the dictionary. + */ + private static Uri getProviderUri(String path) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath( + path).build(); + } + + /** + * Queries a content provider for the list of dictionaries for a specific locale + * available to copy into Latin IME. + */ + private static List getDictIdList(final Locale locale, final Context context) { + final ContentResolver resolver = context.getContentResolver(); + final Uri dictionaryPackUri = getProviderUri(locale); + + final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null, null); + if (null == c) return Collections.emptyList(); + if (c.getCount() <= 0 || !c.moveToFirst()) { + c.close(); + return Collections.emptyList(); + } + + final List list = new ArrayList(); + do { + final String id = c.getString(0); + if (TextUtils.isEmpty(id)) continue; + list.add(id); + } while (c.moveToNext()); + c.close(); + return list; + } + /** * Queries a content provider for dictionary data for some locale and returns the file addresses * @@ -95,20 +135,26 @@ public class BinaryDictionaryFileDumper { * @throw FileNotFoundException if the provider returns non-existent data. * @throw IOException if the provider-returned data could not be read. */ - public static List getDictSetFromContentProvider(Locale locale, - Context context) throws FileNotFoundException, IOException { + public static List getDictSetFromContentProvider(final Locale locale, + final 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)); - afd.close(); - return Arrays.asList(AssetFileAddress.makeFromFileName(fileName)); + final List idList = getDictIdList(locale, context); + final List fileAddressList = new ArrayList(); + for (String id : idList) { + final Uri dictionaryPackUri = getProviderUri(id); + final AssetFileDescriptor afd = + resolver.openAssetFileDescriptor(dictionaryPackUri, "r"); + if (null == afd) continue; + final String fileName = + copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context)); + afd.close(); + fileAddressList.add(AssetFileAddress.makeFromFileName(fileName)); + } + return fileAddressList; } /** diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 989a0e9a0..4b1c05adf 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -63,9 +63,6 @@ class BinaryDictionaryGetter { * Returns a list of file addresses for a given locale, trying relevant methods in order. * * Tries to get binary dictionaries from various sources, in order: - * - Uses a private method of getting a private dictionaries, as implemented by the - * PrivateBinaryDictionaryGetter class. - * If that fails: * - Uses a content provider to get a public dictionary set, as per the protocol described * in BinaryDictionaryFileDumper. * If that fails: @@ -76,31 +73,23 @@ class BinaryDictionaryGetter { */ public static List getDictionaryFiles(Locale locale, Context context, int fallbackResId) { - // Try first to query a private package signed the same way for private files. - final List privateFiles = - PrivateBinaryDictionaryGetter.getDictionaryFiles(locale, context); - if (null != privateFiles) { - return privateFiles; - } else { - try { - // If that was no-go, try to find a publicly exported dictionary. - List listFromContentProvider = - BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context); - if (null != listFromContentProvider) { - return listFromContentProvider; - } - // If the list is null, fall through and return the fallback - } catch (FileNotFoundException e) { - Log.e(TAG, "Unable to create dictionary file from provider for locale " - + locale.toString() + ": falling back to internal dictionary"); - } catch (IOException e) { - Log.e(TAG, "Unable to read source data for locale " - + locale.toString() + ": falling back to internal dictionary"); + try { + List listFromContentProvider = + BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context); + if (null != listFromContentProvider) { + return listFromContentProvider; } - final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId, - locale); - if (null == fallbackAsset) return null; - return Arrays.asList(fallbackAsset); + // If the list is null, fall through and return the fallback + } catch (FileNotFoundException e) { + Log.e(TAG, "Unable to create dictionary file from provider for locale " + + locale.toString() + ": falling back to internal dictionary"); + } catch (IOException e) { + Log.e(TAG, "Unable to read source data for locale " + + locale.toString() + ": falling back to internal dictionary"); } + final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId, + locale); + if (null == fallbackAsset) return null; + return Arrays.asList(fallbackAsset); } } -- cgit v1.2.3-83-g751a From 3a22708e653193ee42d197c8fddbde87ea85d2d5 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Mon, 8 Aug 2011 20:18:41 +0900 Subject: Have Latin IME copy files from the dictionary pack. Also some minor cleanup: remove unused methods, minor refactoring into methods. Bug: 5095140 Change-Id: I035537b37a31adfc8db3b933fb0cefcf703d6c7c --- .../latin/BinaryDictionaryFileDumper.java | 96 +++++++++++----------- 1 file changed, 48 insertions(+), 48 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 1c7599442..b8850680b 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -23,7 +23,6 @@ import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; -import android.util.Log; import java.io.File; import java.io.FileInputStream; @@ -53,38 +52,55 @@ public class 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 + * 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) */ - 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; + // TODO: create a unit test for this method + private static String replaceFileNameDangerousCharacters(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(); + for (int i = 0; i < name.length(); ++i) { + final int codePoint = name.codePointAt(i); + if (Character.isLetterOrDigit(codePoint) || '_' == codePoint) { + sb.appendCodePoint(codePoint); + } else { + sb.append('%'); + sb.append(Integer.toHexString(codePoint)); + } + } + return sb.toString(); } /** - * Return for a given locale the provider URI to query to get the dictionary. + * Find out the cache directory associated with a specific locale. */ - // TODO: remove this - 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 String getCacheDirectoryForLocale(Locale locale, Context context) { + final String directoryName = replaceFileNameDangerousCharacters(locale.toString()); + return context.getFilesDir() + File.separator + directoryName; + } + + /** + * 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 + * @param context the context to use for getting the directory + * @return the name of the file to be created + */ + private static String getCacheFileName(String id, Locale locale, Context context) { + final String fileName = replaceFileNameDangerousCharacters(id); + return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; } /** @@ -102,7 +118,7 @@ public class BinaryDictionaryFileDumper { */ private static List getDictIdList(final Locale locale, final Context context) { final ContentResolver resolver = context.getContentResolver(); - final Uri dictionaryPackUri = getProviderUri(locale); + final Uri dictionaryPackUri = getProviderUri(locale.toString()); final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null, null); if (null == c) return Collections.emptyList(); @@ -137,10 +153,6 @@ public class BinaryDictionaryFileDumper { */ public static List getDictSetFromContentProvider(final Locale locale, final 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 List idList = getDictIdList(locale, context); final List fileAddressList = new ArrayList(); @@ -149,26 +161,14 @@ public class BinaryDictionaryFileDumper { final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(dictionaryPackUri, "r"); if (null == afd) continue; - final String fileName = - copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context)); + final String fileName = copyFileTo(afd.createInputStream(), + getCacheFileName(id, locale, context)); afd.close(); fileAddressList.add(AssetFileAddress.makeFromFileName(fileName)); } return fileAddressList; } - /** - * 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. - */ - public static String getDictionaryFileFromFile(String fileName, Locale locale, - Context context) throws FileNotFoundException, IOException { - return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale, - context)); - } - /** * Accepts a resource number as dictionary data for some locale and returns the name of a file. * @@ -181,7 +181,7 @@ public class BinaryDictionaryFileDumper { final Locale savedLocale = Utils.setSystemLocale(res, locale); final InputStream stream = res.openRawResource(resource); Utils.setSystemLocale(res, savedLocale); - return copyFileTo(stream, getCacheFileNameForLocale(locale, context)); + return copyFileTo(stream, getCacheFileName(Integer.toString(resource), locale, context)); } /** -- cgit v1.2.3-83-g751a From d4c08d9be3540466c8253ee02fecf4b6035d8ee0 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 10 Aug 2011 16:37:46 +0900 Subject: Make sure the directory where to put files actually exists Bug: 5095140 Change-Id: I764471e54ce0bf6aefe5d604cee97639d5ad0af9 --- .../inputmethod/latin/BinaryDictionaryFileDumper.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index b8850680b..2d50a6f46 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; +import android.util.Log; import java.io.File; import java.io.FileInputStream; @@ -40,6 +41,8 @@ import java.util.Locale; * file from the dictionary provider */ public class BinaryDictionaryFileDumper { + private static final String TAG = BinaryDictionaryFileDumper.class.getSimpleName(); + /** * The size of the temporary buffer to copy files. */ @@ -79,8 +82,16 @@ public class BinaryDictionaryFileDumper { * Find out the cache directory associated with a specific locale. */ private static String getCacheDirectoryForLocale(Locale locale, Context context) { - final String directoryName = replaceFileNameDangerousCharacters(locale.toString()); - return context.getFilesDir() + File.separator + directoryName; + final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toString()); + final String absoluteDirectoryName = context.getFilesDir() + 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; } /** -- cgit v1.2.3-83-g751a From 5774908bb30db63f912ae588caebf257251884c8 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 10 Aug 2011 18:57:01 +0900 Subject: Give the orders to delete the source files to the dict pack Bug: 5095140 Change-Id: I64552861768ca30073ffe0d631b2e0c44dad4aa9 --- .../com/android/inputmethod/latin/BinaryDictionaryFileDumper.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 2d50a6f46..f4ba0bcdc 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -168,13 +168,17 @@ public class BinaryDictionaryFileDumper { final List idList = getDictIdList(locale, context); final List fileAddressList = new ArrayList(); for (String id : idList) { - final Uri dictionaryPackUri = getProviderUri(id); + final Uri wordListUri = getProviderUri(id); final AssetFileDescriptor afd = - resolver.openAssetFileDescriptor(dictionaryPackUri, "r"); + resolver.openAssetFileDescriptor(wordListUri, "r"); if (null == afd) continue; final String fileName = copyFileTo(afd.createInputStream(), getCacheFileName(id, locale, context)); afd.close(); + if (0 >= resolver.delete(wordListUri, null, null)) { + // I'd rather not print the word list ID to the log here out of security concerns + Log.e(TAG, "Could not have the dictionary pack delete a word list"); + } fileAddressList.add(AssetFileAddress.makeFromFileName(fileName)); } return fileAddressList; -- cgit v1.2.3-83-g751a From 28966734619251f78812f6a53f5efacbf5f77c49 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Thu, 11 Aug 2011 16:44:36 +0900 Subject: Rename a function and update a comment Bug: 5095140 Change-Id: Idf66a04c6a1a05015f94187a8dbce3d443bbf38b --- .../latin/BinaryDictionaryFileDumper.java | 81 +++------------------- .../inputmethod/latin/BinaryDictionaryGetter.java | 70 +++++++++++++++++-- 2 files changed, 75 insertions(+), 76 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index f4ba0bcdc..3da670e2e 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -25,7 +25,6 @@ import android.net.Uri; import android.text.TextUtils; import android.util.Log; -import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -54,66 +53,6 @@ public class BinaryDictionaryFileDumper { private BinaryDictionaryFileDumper() { } - /** - * 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(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(); - for (int i = 0; i < name.length(); ++i) { - final int codePoint = name.codePointAt(i); - if (Character.isLetterOrDigit(codePoint) || '_' == codePoint) { - sb.appendCodePoint(codePoint); - } else { - sb.append('%'); - sb.append(Integer.toHexString(codePoint)); - } - } - return sb.toString(); - } - - /** - * Find out the cache directory associated with a specific locale. - */ - private static String getCacheDirectoryForLocale(Locale locale, Context context) { - final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toString()); - final String absoluteDirectoryName = context.getFilesDir() + 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 - * @param context the context to use for getting the directory - * @return the name of the file to be created - */ - private static String getCacheFileName(String id, Locale locale, Context context) { - final String fileName = replaceFileNameDangerousCharacters(id); - return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; - } - /** * Return for a given locale or dictionary id the provider URI to get the dictionary. */ @@ -149,20 +88,16 @@ public class BinaryDictionaryFileDumper { } /** - * Queries a content provider for dictionary data for some locale and returns the file addresses + * 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 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. + * 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. */ - public static List getDictSetFromContentProvider(final Locale locale, + public static List cacheDictionariesFromContentProvider(final Locale locale, final Context context) throws FileNotFoundException, IOException { final ContentResolver resolver = context.getContentResolver(); final List idList = getDictIdList(locale, context); @@ -173,7 +108,7 @@ public class BinaryDictionaryFileDumper { resolver.openAssetFileDescriptor(wordListUri, "r"); if (null == afd) continue; final String fileName = copyFileTo(afd.createInputStream(), - getCacheFileName(id, locale, context)); + BinaryDictionaryGetter.getCacheFileName(id, locale, context)); afd.close(); if (0 >= resolver.delete(wordListUri, null, null)) { // I'd rather not print the word list ID to the log here out of security concerns @@ -196,7 +131,9 @@ public class BinaryDictionaryFileDumper { final Locale savedLocale = Utils.setSystemLocale(res, locale); final InputStream stream = res.openRawResource(resource); Utils.setSystemLocale(res, savedLocale); - return copyFileTo(stream, getCacheFileName(Integer.toString(resource), locale, context)); + return copyFileTo(stream, + BinaryDictionaryGetter.getCacheFileName(Integer.toString(resource), + locale, context)); } /** diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 4b1c05adf..b26731ac5 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -21,6 +21,7 @@ import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.util.Log; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; @@ -40,6 +41,66 @@ class BinaryDictionaryGetter { // Prevents this from being instantiated private BinaryDictionaryGetter() {} + /** + * 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(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(); + for (int i = 0; i < name.length(); ++i) { + final int codePoint = name.codePointAt(i); + if (Character.isLetterOrDigit(codePoint) || '_' == codePoint) { + sb.appendCodePoint(codePoint); + } else { + sb.append('%'); + sb.append(Integer.toHexString(codePoint)); + } + } + return sb.toString(); + } + + /** + * Find out the cache directory associated with a specific locale. + */ + private static String getCacheDirectoryForLocale(Locale locale, Context context) { + final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toString()); + final String absoluteDirectoryName = context.getFilesDir() + 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 + * @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, Locale locale, Context context) { + final String fileName = replaceFileNameDangerousCharacters(id); + return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; + } + /** * Returns a file address from a resource, or null if it cannot be opened. */ @@ -74,10 +135,11 @@ class BinaryDictionaryGetter { public static List getDictionaryFiles(Locale locale, Context context, int fallbackResId) { try { - List listFromContentProvider = - BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context); - if (null != listFromContentProvider) { - return listFromContentProvider; + List cachedDictionaryList = + BinaryDictionaryFileDumper.cacheDictionariesFromContentProvider(locale, + context); + if (null != cachedDictionaryList) { + return cachedDictionaryList; } // If the list is null, fall through and return the fallback } catch (FileNotFoundException e) { -- cgit v1.2.3-83-g751a From 80e0bf04292867ddc769aca75ebaee817b95a941 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Tue, 16 Aug 2011 21:35:52 +0900 Subject: Exception refactoring Now that the dictionary pack can return several files, it's better to handle IO exceptions for each file rather than globally. This also will help with next implementation steps. Bug: 5095140 Change-Id: I5ed135ad2ad4f55f61f9b3f92c48a35d5c24bdb2 --- .../latin/BinaryDictionaryFileDumper.java | 29 ++++++++++++++-------- .../inputmethod/latin/BinaryDictionaryGetter.java | 25 +++++++------------ 2 files changed, 28 insertions(+), 26 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 3da670e2e..ed5f83b3b 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -98,23 +98,32 @@ public class BinaryDictionaryFileDumper { * @throw IOException if the provider-returned data could not be read. */ public static List cacheDictionariesFromContentProvider(final Locale locale, - final Context context) throws FileNotFoundException, IOException { + final Context context) { final ContentResolver resolver = context.getContentResolver(); final List idList = getDictIdList(locale, context); final List fileAddressList = new ArrayList(); for (String id : idList) { final Uri wordListUri = getProviderUri(id); - final AssetFileDescriptor afd = - resolver.openAssetFileDescriptor(wordListUri, "r"); + AssetFileDescriptor afd = null; + try { + afd = resolver.openAssetFileDescriptor(wordListUri, "r"); + } catch (FileNotFoundException e) { + // leave null inside afd and continue + } if (null == afd) continue; - final String fileName = copyFileTo(afd.createInputStream(), - BinaryDictionaryGetter.getCacheFileName(id, locale, context)); - afd.close(); - if (0 >= resolver.delete(wordListUri, null, null)) { - // I'd rather not print the word list ID to the log here out of security concerns - Log.e(TAG, "Could not have the dictionary pack delete a word list"); + try { + final String fileName = copyFileTo(afd.createInputStream(), + BinaryDictionaryGetter.getCacheFileName(id, locale, context)); + afd.close(); + 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); } - fileAddressList.add(AssetFileAddress.makeFromFileName(fileName)); } return fileAddressList; } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 360cf21ca..9b89b9acf 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -214,23 +214,16 @@ class BinaryDictionaryGetter { */ public static List getDictionaryFiles(final Locale locale, final Context context, final int fallbackResId) { - try { - // cacheDictionariesFromContentProvider 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); - List cachedDictionaryList = getCachedDictionaryList(locale, context); - if (null != cachedDictionaryList) { - return cachedDictionaryList; - } - // If the list is null, fall through and return the fallback - } catch (FileNotFoundException e) { - Log.e(TAG, "Unable to create dictionary file from provider for locale " - + locale.toString() + ": falling back to internal dictionary"); - } catch (IOException e) { - Log.e(TAG, "Unable to read source data for locale " - + locale.toString() + ": falling back to internal dictionary"); + + // cacheDictionariesFromContentProvider 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); + List cachedDictionaryList = getCachedDictionaryList(locale, context); + if (null != cachedDictionaryList) { + return cachedDictionaryList; } + final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId, locale); if (null == fallbackAsset) return null; -- cgit v1.2.3-83-g751a From 7b1f74bb9ddae952f4da6c8d9bbb0057984b0988 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 24 Aug 2011 12:45:52 +0900 Subject: Refactoring: cut out a method for caching a word list This is preparation to have the decrypting/unzipping code moved over to LatinIME. Bug: 5095140 Change-Id: Ic3fdcc3de673b46cef2eb9ebe6a52cbdd614e50a --- .../latin/BinaryDictionaryFileDumper.java | 66 +++++++++++++--------- .../inputmethod/latin/BinaryDictionaryGetter.java | 12 ++-- 2 files changed, 44 insertions(+), 34 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index ed5f83b3b..1f756eafb 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 getDictIdList(final Locale locale, final Context context) { + private static List getWordListIds(final Locale locale, final Context context) { final ContentResolver resolver = context.getContentResolver(); final Uri dictionaryPackUri = getProviderUri(locale.toString()); @@ -88,41 +88,51 @@ public class BinaryDictionaryFileDumper { } /** - * Queries a content provider for dictionary data for some locale and cache the returned files + * Caches a word list the id of which is passed as an argument. + */ + private static AssetFileAddress cacheWordList(final String id, final Locale locale, + final ContentResolver resolver, final Context context) { + final Uri wordListUri = getProviderUri(id); + try { + final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(wordListUri, "r"); + if (null == afd) return null; + final String fileName = copyFileTo(afd.createInputStream(), + BinaryDictionaryGetter.getCacheFileName(id, locale, context)); + afd.close(); + 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"); + } + return AssetFileAddress.makeFromFileName(fileName); + } catch (FileNotFoundException e) { + // This may only come from openAssetFileDescriptor + return null; + } catch (IOException e) { + // Can't read the file for some reason. + Log.e(TAG, "Cannot read a word list from the dictionary pack : " + e); + } + return null; + } + + /** + * Queries a content provider for word list 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 + * 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 files, or null if no data could be obtained. + * @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 List cacheDictionariesFromContentProvider(final Locale locale, + public static List cacheWordListsFromContentProvider(final Locale locale, final Context context) { final ContentResolver resolver = context.getContentResolver(); - final List idList = getDictIdList(locale, context); + final List idList = getWordListIds(locale, context); final List fileAddressList = new ArrayList(); for (String id : idList) { - final Uri wordListUri = getProviderUri(id); - 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(); - 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); + final AssetFileAddress afd = cacheWordList(id, locale, resolver, context); + if (null != afd) { + fileAddressList.add(afd); } } return fileAddressList; 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 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 fileList = new ArrayList(); - // 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; -- cgit v1.2.3-83-g751a From 38d512c0b24f3e5ce595e0b4b9092f84af380517 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 24 Aug 2011 16:32:29 +0900 Subject: Call the decrypt/unzip routines upon copying a dictionary. Bug: 5095140 Change-Id: I7000f752bc9b7fd6a7af4839b2f225c085300128 --- .../latin/BinaryDictionaryFileDumper.java | 143 +++++++++++++++------ .../android/inputmethod/latin/FileTransforms.java | 40 ++++++ 2 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 java/src/com/android/inputmethod/latin/FileTransforms.java (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 1f756eafb..24bb7b78a 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -87,29 +87,112 @@ public class BinaryDictionaryFileDumper { return list; } + /** - * Caches a word list the id of which is passed as an argument. + * Helper method to encapsulate exception handling. + */ + 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); - try { - final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(wordListUri, "r"); - if (null == afd) return null; - final String fileName = copyFileTo(afd.createInputStream(), - BinaryDictionaryGetter.getCacheFileName(id, locale, context)); - afd.close(); - 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"); + 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 { + // 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)) { + Log.e(TAG, "Could not have the dictionary pack delete a word list"); + } + // 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 AssetFileAddress.makeFromFileName(fileName); - } catch (FileNotFoundException e) { - // This may only come from openAssetFileDescriptor - return null; - } catch (IOException e) { - // Can't read the file for some reason. - Log.e(TAG, "Cannot read a word list from the dictionary pack : " + 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 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; } @@ -139,35 +222,15 @@ public class BinaryDictionaryFileDumper { } /** - * Accepts a resource number as dictionary data for some locale and returns the name of a file. - * - * This will make the resource the cached dictionary for this locale, overwriting any previous - * cached data. - */ - 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)); - } - - /** - * 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/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); + } +} -- cgit v1.2.3-83-g751a From 20973de72b8e3ba607d17e50bb4fbc3cc815a6eb Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 24 Aug 2011 20:27:04 +0900 Subject: Avoid unnecessary output to the system log. The message this removes gets printed under normal conditions. Normally dictionary files are compressed then crypted, but not compressed a second time; however LatinIME tries to open a compressed-crypted-compressed file first, because it could not do it afterwards and we want to support this case. So under normal operations, the first method LatinIME tries is actually expected to fail. Also, if we decide to stop compressing or/and encrypting dicts LatinIME supports it as a valid use case. It should not print errors to the log. If the file cannot be open at all, then it is an unexpected case, and Latin IME still reports to the log. Change-Id: Ic5228c51365a101af1d03e2c893484d3050b5a1c --- .../src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 24bb7b78a..a4f20181c 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -168,7 +168,9 @@ public class BinaryDictionaryFileDumper { // 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); + if (DEBUG) { + Log.i(TAG, "Can't open word list in mode " + mode + " : " + e); + } // Try the next method. } finally { // Ignore exceptions while closing files. -- cgit v1.2.3-83-g751a From a16621ada43c7b499857bc8967e454994098bff3 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 24 Aug 2011 21:36:18 +0900 Subject: Add a debug variable ...that was missing Change-Id: Ia3b17909f295e0a93dee633c8d67c0e03840023e --- java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java | 1 + 1 file changed, 1 insertion(+) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index a4f20181c..89944407e 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -41,6 +41,7 @@ import java.util.Locale; */ 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. -- cgit v1.2.3-83-g751a From de4e8dedccc7b6db6df4c3f75d9f2458432c558a Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Thu, 25 Aug 2011 18:04:21 +0900 Subject: Allow sharing dictionaries between similar locales. Bug: 5058488 Change-Id: Ib12013f58afad957a8205b439f87480cc12ea06f --- .../latin/BinaryDictionaryFileDumper.java | 39 +++-- .../inputmethod/latin/BinaryDictionaryGetter.java | 63 +++++++-- .../com/android/inputmethod/latin/LocaleUtils.java | 157 +++++++++++++++++++++ .../android/inputmethod/latin/WordListInfo.java | 29 ++++ 4 files changed, 259 insertions(+), 29 deletions(-) create mode 100644 java/src/com/android/inputmethod/latin/LocaleUtils.java create mode 100644 java/src/com/android/inputmethod/latin/WordListInfo.java (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 89944407e..e95172d1f 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -67,25 +67,34 @@ public class BinaryDictionaryFileDumper { * Queries a content provider for the list of word lists for a specific locale * available to copy into Latin IME. */ - private static List getWordListIds(final Locale locale, final Context context) { + private static List getWordListWordListInfos(final Locale locale, + final Context context) { final ContentResolver resolver = context.getContentResolver(); final Uri dictionaryPackUri = getProviderUri(locale.toString()); final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null, null); - if (null == c) return Collections.emptyList(); + if (null == c) return Collections.emptyList(); if (c.getCount() <= 0 || !c.moveToFirst()) { c.close(); - return Collections.emptyList(); + return Collections.emptyList(); } - final List list = new ArrayList(); - do { - final String id = c.getString(0); - if (TextUtils.isEmpty(id)) continue; - list.add(id); - } while (c.moveToNext()); - c.close(); - return list; + try { + final List list = new ArrayList(); + 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.emptyList(); + } } @@ -108,7 +117,7 @@ public class BinaryDictionaryFileDumper { * 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, + private static AssetFileAddress cacheWordList(final String id, final String locale, final ContentResolver resolver, final Context context) { final int COMPRESSED_CRYPTED_COMPRESSED = 0; @@ -213,10 +222,10 @@ public class BinaryDictionaryFileDumper { public static List cacheWordListsFromContentProvider(final Locale locale, final Context context) { final ContentResolver resolver = context.getContentResolver(); - final List idList = getWordListIds(locale, context); + final List idList = getWordListWordListInfos(locale, context); final List fileAddressList = new ArrayList(); - for (String id : idList) { - final AssetFileAddress afd = cacheWordList(id, locale, resolver, context); + for (WordListInfo id : idList) { + final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context); if (null != afd) { fileAddressList.add(afd); } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 38344300c..360c944d2 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -108,12 +108,19 @@ class BinaryDictionaryGetter { 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(Locale locale, Context context) { - final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toString()); - final String absoluteDirectoryName = context.getFilesDir() + File.separator + 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()) { @@ -135,11 +142,11 @@ class BinaryDictionaryGetter { * 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 + * @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, Locale locale, Context context) { + public static String getCacheFileName(String id, String locale, Context context) { final String fileName = replaceFileNameDangerousCharacters(id); return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; } @@ -198,26 +205,54 @@ 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 list of cached files for a specific locale. * - * @param locale the locale to find the dictionary files for. + * @param locale the locale to find the dictionary files for, as a string. * @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 Locale locale, + private static File[] getCachedWordLists(final String locale, final Context context) { - final String directoryName = getCacheDirectoryForLocale(locale, context); - final File[] cacheFiles = new File(directoryName).listFiles(); - if (null == cacheFiles) return EMPTY_FILE_ARRAY; - return cacheFiles; + final File[] directoryList = getCachedDirectoryList(context); + if (null == directoryList) return EMPTY_FILE_ARRAY; + final ArrayList cacheFiles = new ArrayList(); + for (File directory : directoryList) { + if (!directory.isDirectory()) continue; + final String dirLocale = getWordListIdFromFileName(directory.getName()); + if (LocaleUtils.isMatch(LocaleUtils.getMatchLevel(dirLocale, locale))) { + final File[] wordLists = directory.listFiles(); + if (null != wordLists) { + for (File wordList : wordLists) { + cacheFiles.add(wordList); + } + } + } + } + if (cacheFiles.isEmpty()) return EMPTY_FILE_ARRAY; + return cacheFiles.toArray(EMPTY_FILE_ARRAY); } /** - * Returns the id of the main dict for a specified locale. + * 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) { - return locale.toString(); + // 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 locale.getLanguage().toString(); } /** @@ -239,7 +274,7 @@ class BinaryDictionaryGetter { // 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); - final File[] cachedWordLists = getCachedWordLists(locale, context); + final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); final String mainDictId = getMainDictId(locale); diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java new file mode 100644 index 000000000..054f1f9b8 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java @@ -0,0 +1,157 @@ +/* + * 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.text.TextUtils; + +/** + * A class to help with handling Locales in string form. + * + * This file has the same meaning and features (and shares all of its code) with + * the one in the dictionary pack. They need to be kept synchronized; for any + * update/bugfix to this file, consider also updating/fixing the version in the + * dictionary pack. + */ +public class LocaleUtils { + + private final static String TAG = LocaleUtils.class.getSimpleName(); + + // Locale match level constants. + // A higher level of match is guaranteed to have a higher numerical value. + // Some room is left within constants to add match cases that may arise necessary + // in the future, for example differentiating between the case where the countries + // are both present and different, and the case where one of the locales does not + // specify the countries. This difference is not needed now. + + // Nothing matches. + public static final int LOCALE_NO_MATCH = 0; + // The languages matches, but the country are different. Or, the reference locale requires a + // country and the tested locale does not have one. + public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3; + // The languages and country match, but the variants are different. Or, the reference locale + // requires a variant and the tested locale does not have one. + public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6; + // The required locale is null or empty so it will accept anything, and the tested locale + // is non-null and non-empty. + public static final int LOCALE_ANY_MATCH = 10; + // The language matches, and the tested locale specifies a country but the reference locale + // does not require one. + public static final int LOCALE_LANGUAGE_MATCH = 15; + // The language and the country match, and the tested locale specifies a variant but the + // reference locale does not require one. + public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20; + // The compared locales are fully identical. This is the best match level. + public static final int LOCALE_FULL_MATCH = 30; + + // The level at which a match is "normally" considered a locale match with standard algorithms. + // Don't use this directly, use #isMatch to test. + private static final int LOCALE_MATCH = LOCALE_ANY_MATCH; + + // Make this match the maximum match level. If this evolves to have more than 2 digits + // when written in base 10, also adjust the getMatchLevelSortedString method. + private static final int MATCH_LEVEL_MAX = 30; + + /** + * Return how well a tested locale matches a reference locale. + * + * This will check the tested locale against the reference locale and return a measure of how + * a well it matches the reference. The general idea is that the tested locale has to match + * every specified part of the required locale. A full match occur when they are equal, a + * partial match when the tested locale agrees with the reference locale but is more specific, + * and a difference when the tested locale does not comply with all requirements from the + * reference locale. + * In more detail, if the reference locale specifies at least a language and the testedLocale + * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the + * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH + * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and + * tested locale agree on the language, but not on the country, + * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country, + * and LOCALE_LANGUAGE_MATCH otherwise. + * If they agree on both the language and the country, but not on the variant, + * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale + * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches, + * LOCALE_FULL_MATCH is returned. + * Examples: + * en <=> en_US => LOCALE_LANGUAGE_MATCH + * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER + * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER + * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH + * sp_US <=> en_US => LOCALE_NO_MATCH + * de <=> de => LOCALE_FULL_MATCH + * en_US <=> en_US => LOCALE_FULL_MATCH + * "" <=> en_US => LOCALE_ANY_MATCH + * + * @param referenceLocale the reference locale to test against. + * @param testedLocale the locale to test. + * @return a constant that measures how well the tested locale matches the reference locale. + */ + public static int getMatchLevel(String referenceLocale, String testedLocale) { + if (TextUtils.isEmpty(referenceLocale)) { + return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH; + } + if (null == testedLocale) return LOCALE_NO_MATCH; + String[] referenceParams = referenceLocale.split("_", 3); + String[] testedParams = testedLocale.split("_", 3); + // By spec of String#split, [0] cannot be null and length cannot be 0. + if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH; + switch (referenceParams.length) { + case 1: + return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH; + case 2: + if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (!referenceParams[1].equals(testedParams[1])) + return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH; + return LOCALE_FULL_MATCH; + case 3: + if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (!referenceParams[1].equals(testedParams[1])) + return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; + if (!referenceParams[2].equals(testedParams[2])) + return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; + return LOCALE_FULL_MATCH; + } + // It should be impossible to come here + return LOCALE_NO_MATCH; + } + + /** + * Return a string that represents this match level, with better matches first. + * + * The strings are sorted in lexicographic order: a better match will always be less than + * a worse match when compared together. + */ + public static String getMatchLevelSortedString(int matchLevel) { + // This works because the match levels are 0~99 (actually 0~30) + // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel + return String.format("%02d", MATCH_LEVEL_MAX - matchLevel); + } + + /** + * Find out whether a match level should be considered a match. + * + * This method takes a match level as returned by the #getMatchLevel method, and returns whether + * it should be considered a match in the usual sense with standard Locale functions. + * + * @param level the match level, as returned by getMatchLevel. + * @return whether this is a match or not. + */ + public static boolean isMatch(int level) { + return LOCALE_MATCH <= level; + } +} diff --git a/java/src/com/android/inputmethod/latin/WordListInfo.java b/java/src/com/android/inputmethod/latin/WordListInfo.java new file mode 100644 index 000000000..54f04d78f --- /dev/null +++ b/java/src/com/android/inputmethod/latin/WordListInfo.java @@ -0,0 +1,29 @@ +/** + * 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; + +/** + * Information container for a word list. + */ +public class WordListInfo { + public final String mId; + public final String mLocale; + public WordListInfo(final String id, final String locale) { + mId = id; + mLocale = locale; + } +} -- cgit v1.2.3-83-g751a From 7a408431fe6b77bc7a317b86c7adfb1aeebd5def Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Tue, 30 Aug 2011 21:46:33 +0900 Subject: Check the magic number of a decoded file Checking the magic number of a file upon decoding is necessary, because if the file is corrupt and we don't check it, we will fall back to a simple copy of the corrupted file. Latin IME would realize this and would not crash, but would not use the corrupted dictionary. If this happened to be a main dictionary, then the user would lose the ability to use the correct built-in dictionary. Not the same, but kinda similar to Bug: 5223031 Change-Id: Ic2783dc9dd5f3dcf2865623d9452765fe3778db7 --- .../latin/BinaryDictionaryFileDumper.java | 42 ++++++++++++++++++---- 1 file changed, 36 insertions(+), 6 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index e95172d1f..38563be4a 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -25,12 +25,15 @@ 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; @@ -46,7 +49,9 @@ public class BinaryDictionaryFileDumper { /** * 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 = new byte[] { 0x78, (byte)0xB1 }; private static final String DICTIONARY_PROJECTION[] = { "id" }; @@ -135,6 +140,7 @@ public class BinaryDictionaryFileDumper { for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) { InputStream originalSourceStream = null; InputStream inputStream = null; + File outputFile = null; FileOutputStream outputStream = null; AssetFileDescriptor afd = null; try { @@ -144,7 +150,8 @@ public class BinaryDictionaryFileDumper { if (null == afd) return null; originalSourceStream = afd.createInputStream(); // Open output. - outputStream = new FileOutputStream(outputFileName); + outputFile = new File(outputFileName); + outputStream = new FileOutputStream(outputFile); // Get the appropriate decryption method for this try switch (mode) { case COMPRESSED_CRYPTED_COMPRESSED: @@ -171,7 +178,7 @@ public class BinaryDictionaryFileDumper { inputStream = originalSourceStream; break; } - copyFileTo(inputStream, outputStream); + checkMagicAndCopyFileTo(new BufferedInputStream(inputStream), outputStream); if (0 >= resolver.delete(wordListUri, null, null)) { Log.e(TAG, "Could not have the dictionary pack delete a word list"); } @@ -181,6 +188,12 @@ public class BinaryDictionaryFileDumper { 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. @@ -234,12 +247,29 @@ public class BinaryDictionaryFileDumper { } /** - * Copies the data in an input stream to a target file. + * 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 outputFile an outputstream to copy the data to. */ - private static void copyFileTo(final InputStream input, final FileOutputStream output) - throws FileNotFoundException, IOException { + private static void checkMagicAndCopyFileTo(final BufferedInputStream input, + final FileOutputStream output) throws FileNotFoundException, IOException { + // Check the magic number + final byte[] magicNumberBuffer = new byte[MAGIC_NUMBER.length]; + final int readMagicNumberSize = input.read(magicNumberBuffer, 0, MAGIC_NUMBER.length); + if (readMagicNumberSize < MAGIC_NUMBER.length) { + throw new IOException("Less bytes to read than the magic number length"); + } + if (!Arrays.equals(MAGIC_NUMBER, magicNumberBuffer)) { + throw new IOException("Wrong magic number for downloaded file"); + } + output.write(MAGIC_NUMBER); + + // Actually copy the file final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE]; for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) output.write(buffer, 0, readBytes); -- cgit v1.2.3-83-g751a From ab72a97d7ce44230a0c824797d1675a5ca354a56 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Tue, 4 Oct 2011 10:54:23 +0900 Subject: Cleanup unused import This change also gets rid of several compiler warnings. Change-Id: I23962edaadad18a6e0395d528af17b909dcf5dad --- .../accessibility/AccessibleKeyboardViewProxy.java | 1 - .../inputmethod/keyboard/KeyboardSwitcher.java | 1 + .../inputmethod/keyboard/ProximityInfo.java | 2 -- .../inputmethod/latin/AssetFileAddress.java | 8 +++--- .../inputmethod/latin/BinaryDictionary.java | 9 ++----- .../latin/BinaryDictionaryFileDumper.java | 2 -- .../inputmethod/latin/BinaryDictionaryGetter.java | 3 --- .../inputmethod/latin/ExpandableDictionary.java | 31 ++++++++++------------ .../android/inputmethod/latin/FileTransforms.java | 2 -- .../com/android/inputmethod/latin/LocaleUtils.java | 3 --- .../latin/SynchronouslyLoadedUserDictionary.java | 2 +- .../inputmethod/latin/UserBigramDictionary.java | 12 ++++----- .../spellcheck/AndroidSpellCheckerService.java | 6 +---- .../latin/spellcheck/DictionaryPool.java | 3 +-- .../spellcheck/SpellCheckerProximityInfo.java | 1 - .../spellcheck/SpellCheckerSettingsActivity.java | 4 --- .../spellcheck/SpellCheckerSettingsFragment.java | 6 ++--- 17 files changed, 32 insertions(+), 64 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java index 8185619f9..e1b778126 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java @@ -30,7 +30,6 @@ import android.view.inputmethod.EditorInfo; import com.android.inputmethod.compat.AccessibilityEventCompatUtils; import com.android.inputmethod.compat.AudioManagerCompatWrapper; -import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.compat.MotionEventCompatUtils; import com.android.inputmethod.keyboard.Key; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index e9a7fd077..49e92fd2b 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -317,6 +317,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } final boolean settingsKeyEnabled = settingsValues.isSettingsKeyEnabled(); + @SuppressWarnings("deprecation") final boolean noMicrophone = Utils.inPrivateImeOptions( mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo) || Utils.inPrivateImeOptions( diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index 34a77e1ca..2a25d0ca7 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -18,9 +18,7 @@ package com.android.inputmethod.keyboard; import android.graphics.Rect; -import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.internal.KeyboardParams.TouchPositionCorrection; -import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java index 074ecacc5..3549a1561 100644 --- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java +++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java @@ -37,16 +37,16 @@ class AssetFileAddress { public static AssetFileAddress makeFromFileName(final String filename) { if (null == filename) return null; - File f = new File(filename); - if (null == f || !f.isFile()) return null; + final File f = new File(filename); + if (!f.isFile()) return null; return new AssetFileAddress(filename, 0l, f.length()); } public static AssetFileAddress makeFromFileNameAndOffset(final String filename, final long offset, final long length) { if (null == filename) return null; - File f = new File(filename); - if (null == f || !f.isFile()) return null; + final File f = new File(filename); + if (!f.isFile()) return null; return new AssetFileAddress(filename, offset, length); } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 18a9e3ab1..b9fd57434 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -16,12 +16,10 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.keyboard.ProximityInfo; - import android.content.Context; +import com.android.inputmethod.keyboard.ProximityInfo; + import java.util.Arrays; /** @@ -41,7 +39,6 @@ public class BinaryDictionary extends Dictionary { public static final int MAX_WORD_LENGTH = 48; public static final int MAX_WORDS = 18; - @SuppressWarnings("unused") private static final String TAG = "BinaryDictionary"; private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; private static final int MAX_BIGRAMS = 60; @@ -56,8 +53,6 @@ public class BinaryDictionary extends Dictionary { private final int[] mScores = new int[MAX_WORDS]; private final int[] mBigramScores = new int[MAX_BIGRAMS]; - private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance(); - public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING = new Flag(R.bool.config_require_umlaut_processing, 0x1); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 38563be4a..9ffc7d0a2 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; -import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; @@ -27,7 +26,6 @@ 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; diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 5546d48c0..b333e4873 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -24,9 +24,6 @@ import android.content.res.Resources; import android.util.Log; import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 2b78b9065..cad69bb0e 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -17,7 +17,6 @@ package com.android.inputmethod.latin; import android.content.Context; -import android.os.AsyncTask; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.ProximityInfo; @@ -166,15 +165,14 @@ public class ExpandableDictionary extends Dictionary { // Does children have the current character? final int childrenLength = children.mLength; Node childNode = null; - boolean found = false; for (int i = 0; i < childrenLength; i++) { - childNode = children.mData[i]; - if (childNode.mCode == c) { - found = true; + final Node node = children.mData[i]; + if (node.mCode == c) { + childNode = node; break; } } - if (!found) { + if (childNode == null) { childNode = new Node(); childNode.mCode = c; childNode.mParent = parentNode; @@ -206,7 +204,7 @@ public class ExpandableDictionary extends Dictionary { } protected final void getWordsInner(final WordComposer codes, final WordCallback callback, - final ProximityInfo proximityInfo) { + @SuppressWarnings("unused") final ProximityInfo proximityInfo) { mInputLength = codes.size(); if (mCodes.length < mInputLength) mCodes = new int[mInputLength][]; // Cache the codes so that we don't have to lookup an array list @@ -270,7 +268,7 @@ public class ExpandableDictionary extends Dictionary { */ // TODO: Share this routine with the native code for BinaryDictionary protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word, - final int depth, boolean completion, int snr, int inputIndex, int skipPos, + final int depth, final boolean completion, int snr, int inputIndex, int skipPos, WordCallback callback) { final int count = roots.mLength; final int codeSize = mInputLength; @@ -278,9 +276,9 @@ public class ExpandableDictionary extends Dictionary { if (depth > mMaxDepth) { return; } - int[] currentChars = null; + final int[] currentChars; if (codeSize <= inputIndex) { - completion = true; + currentChars = null; } else { currentChars = mCodes[inputIndex]; } @@ -292,7 +290,7 @@ public class ExpandableDictionary extends Dictionary { final boolean terminal = node.mTerminal; final NodeArray children = node.mChildren; final int freq = node.mFrequency; - if (completion) { + if (completion || currentChars == null) { word[depth] = c; if (terminal) { final int finalFreq; @@ -307,7 +305,7 @@ public class ExpandableDictionary extends Dictionary { } } if (children != null) { - getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, + getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex, skipPos, callback); } } else if ((c == Keyboard.CODE_SINGLE_QUOTE @@ -412,15 +410,14 @@ public class ExpandableDictionary extends Dictionary { // Does children have the current character? final int childrenLength = children.mLength; Node childNode = null; - boolean found = false; for (int i = 0; i < childrenLength; i++) { - childNode = children.mData[i]; - if (childNode.mCode == c) { - found = true; + final Node node = children.mData[i]; + if (node.mCode == c) { + childNode = node; break; } } - if (!found) { + if (childNode == null) { childNode = new Node(); childNode.mCode = c; childNode.mParent = parentNode; diff --git a/java/src/com/android/inputmethod/latin/FileTransforms.java b/java/src/com/android/inputmethod/latin/FileTransforms.java index d0374e01e..80159521c 100644 --- a/java/src/com/android/inputmethod/latin/FileTransforms.java +++ b/java/src/com/android/inputmethod/latin/FileTransforms.java @@ -16,8 +16,6 @@ package com.android.inputmethod.latin; -import android.util.Log; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java index efa9bfee3..e05b47cb7 100644 --- a/java/src/com/android/inputmethod/latin/LocaleUtils.java +++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java @@ -32,9 +32,6 @@ import java.util.Locale; * dictionary pack. */ public class LocaleUtils { - - private final static String TAG = LocaleUtils.class.getSimpleName(); - private LocaleUtils() { // Intentional empty constructor for utility class. } diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java index b526fe510..e52b46ac0 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java @@ -32,7 +32,7 @@ public class SynchronouslyLoadedUserDictionary extends UserDictionary { } @Override - public void getWords(final WordComposer codes, final WordCallback callback, + public synchronized void getWords(final WordComposer codes, final WordCallback callback, final ProximityInfo proximityInfo) { blockingReloadDictionaryIfRequired(); getWordsInner(codes, callback, proximityInfo); diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java index 5b615ca29..9e656675e 100644 --- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java @@ -104,12 +104,12 @@ public class UserBigramDictionary extends ExpandableDictionary { private static class Bigram { public final String mWord1; public final String mWord2; - public final int frequency; + public final int mFrequency; Bigram(String word1, String word2, int frequency) { this.mWord1 = word1; this.mWord2 = word2; - this.frequency = frequency; + this.mFrequency = frequency; } @Override @@ -190,7 +190,7 @@ public class UserBigramDictionary extends ExpandableDictionary { // Nothing pending? Return if (mPendingWrites.isEmpty()) return; // Create a background thread to write the pending entries - new UpdateDbTask(getContext(), sOpenHelper, mPendingWrites, mLocale).execute(); + new UpdateDbTask(sOpenHelper, mPendingWrites, mLocale).execute(); // Create a new map for writing new entries into while the old one is written to db mPendingWrites = new HashSet(); } @@ -302,8 +302,8 @@ public class UserBigramDictionary extends ExpandableDictionary { private final DatabaseHelper mDbHelper; private final String mLocale; - public UpdateDbTask(Context context, DatabaseHelper openHelper, - HashSet pendingWrites, String locale) { + public UpdateDbTask(DatabaseHelper openHelper, HashSet pendingWrites, + String locale) { mMap = pendingWrites; mLocale = locale; mDbHelper = openHelper; @@ -372,7 +372,7 @@ public class UserBigramDictionary extends ExpandableDictionary { c.close(); // insert new frequency - db.insert(FREQ_TABLE_NAME, null, getFrequencyContentValues(pairId, bi.frequency)); + db.insert(FREQ_TABLE_NAME, null, getFrequencyContentValues(pairId, bi.mFrequency)); } checkPruneData(db); sUpdatingDB = false; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 9e030eb90..b197c5bea 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -19,14 +19,12 @@ package com.android.inputmethod.latin.spellcheck; import android.content.Intent; import android.content.res.Resources; import android.service.textservice.SpellCheckerService; -import android.service.textservice.SpellCheckerService.Session; +import android.text.TextUtils; import android.util.Log; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; -import android.text.TextUtils; import com.android.inputmethod.compat.ArraysCompatUtils; -import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.Dictionary; @@ -38,7 +36,6 @@ import com.android.inputmethod.latin.Flag; import com.android.inputmethod.latin.LocaleUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary; -import com.android.inputmethod.latin.UserDictionary; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.WordComposer; @@ -111,7 +108,6 @@ public class AndroidSpellCheckerService extends SpellCheckerService { } } - private final int DEFAULT_SUGGESTION_LENGTH = 16; private final ArrayList mSuggestions; private final int[] mScores; private final String mOriginalText; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java index dec18c1d5..8fc632ee7 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java @@ -16,14 +16,13 @@ package com.android.inputmethod.latin.spellcheck; -import android.content.Context; - import java.util.Locale; import java.util.concurrent.LinkedBlockingQueue; /** * A blocking queue that creates dictionaries up to a certain limit as necessary. */ +@SuppressWarnings("serial") public class DictionaryPool extends LinkedBlockingQueue { private final AndroidSpellCheckerService mService; private final int mMaxSize; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java index abcf7e52a..d5b04b27c 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin.spellcheck; import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.ProximityInfo; -import java.util.Map; import java.util.TreeMap; public class SpellCheckerProximityInfo { diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java index 483679a55..e14db8797 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java @@ -16,14 +16,10 @@ package com.android.inputmethod.latin.spellcheck; -import com.android.inputmethod.latin.R; - import android.content.Intent; import android.os.Bundle; import android.preference.PreferenceActivity; -import java.util.List; - /** * Spell checker preference screen. */ diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java index 9b821be35..7056874a1 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java @@ -16,17 +16,15 @@ package com.android.inputmethod.latin.spellcheck; -import com.android.inputmethod.latin.R; - import android.os.Bundle; import android.preference.PreferenceFragment; +import com.android.inputmethod.latin.R; + /** * Preference screen. */ public class SpellCheckerSettingsFragment extends PreferenceFragment { - private static final String TAG = SpellCheckerSettingsFragment.class.getSimpleName(); - /** * Empty constructor for fragment generation. */ -- cgit v1.2.3-83-g751a From bc89f5d3a5a2771b56643e1b9c47744268fff3a6 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 21 Mar 2012 17:28:37 +0900 Subject: Correctly close the gzip stream I had gotten the chain call in reverse. Bug: 6177523 Change-Id: Ic5b76425de2d5d05d5132f8d2c7ade0d79c14ccc --- .../src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 9ffc7d0a2..8ec440500 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -196,8 +196,8 @@ public class BinaryDictionaryFileDumper { } finally { // Ignore exceptions while closing files. try { - // afd.close() will close inputStream, we should not call inputStream.close(). - if (null != afd) afd.close(); + // 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); } -- cgit v1.2.3-83-g751a From c61cd79229b1871d0f603a23389695d7f7751e66 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Thu, 29 Mar 2012 15:07:53 +0900 Subject: Fix some obvious compiler warnings Change-Id: I10a634f7492b45d5a72345f14d36cf341946387d --- .../inputmethod/compat/InputMethodManagerCompatWrapper.java | 9 +++++---- .../com/android/inputmethod/compat/SuggestionSpanUtils.java | 2 +- .../inputmethod/compat/SuggestionsInfoCompatUtils.java | 4 ++-- .../android/inputmethod/keyboard/KeyboardActionListener.java | 12 ++++++------ .../inputmethod/latin/BinaryDictionaryFileDumper.java | 2 +- java/src/com/android/inputmethod/latin/Dictionary.java | 2 +- java/src/com/android/inputmethod/latin/LatinIME.java | 1 - java/src/com/android/inputmethod/latin/LatinImeLogger.java | 1 - java/src/com/android/inputmethod/latin/ResearchLogger.java | 2 +- java/src/com/android/inputmethod/latin/WordComposer.java | 4 +--- 10 files changed, 18 insertions(+), 21 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java index bf5f20158..a4ff8238c 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java @@ -267,12 +267,13 @@ public class InputMethodManagerCompatWrapper { final InputMethodSubtypeCompatWrapper currentIms = getCurrentInputMethodSubtype(); final List imiList = getEnabledInputMethodList(); imiList.remove(myImi); + final PackageManager pm = mPackageManager; Collections.sort(imiList, new Comparator() { @Override public int compare(InputMethodInfoCompatWrapper imi1, InputMethodInfoCompatWrapper imi2) { - final CharSequence imiId1 = imi1.loadLabel(mPackageManager) + "/" + imi1.getId(); - final CharSequence imiId2 = imi2.loadLabel(mPackageManager) + "/" + imi2.getId(); + final CharSequence imiId1 = imi1.loadLabel(pm) + "/" + imi1.getId(); + final CharSequence imiId2 = imi2.loadLabel(pm) + "/" + imi2.getId(); return imiId1.toString().compareTo(imiId2.toString()); } }); @@ -302,6 +303,7 @@ public class InputMethodManagerCompatWrapper { index++; } + final InputMethodServiceCompatWrapper service = mService; final OnClickListener buttonListener = new OnClickListener() { @Override public void onClick(DialogInterface di, int whichButton) { @@ -309,10 +311,9 @@ public class InputMethodManagerCompatWrapper { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mService.startActivity(intent); + service.startActivity(intent); } }; - final InputMethodServiceCompatWrapper service = mService; final IBinder token = service.getWindow().getWindow().getAttributes().token; final OnClickListener selectionListener = new OnClickListener() { @Override diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java index a9e48404a..df55aee94 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java @@ -129,7 +129,7 @@ public class SuggestionSpanUtils { // TODO: Share the implementation for checking typed word validity between the IME // and the spell checker. final int flag = (sameAsTyped && !suggestedWords.mTypedWordValid) - ? ((int)OBJ_FLAG_EASY_CORRECT | (int)OBJ_FLAG_MISSPELLED) + ? (OBJ_FLAG_EASY_CORRECT | OBJ_FLAG_MISSPELLED) : 0; final Object[] args = diff --git a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java index 6a0d4dd9e..723ec2862 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionsInfoCompatUtils.java @@ -24,7 +24,7 @@ public class SuggestionsInfoCompatUtils { private static final Field FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = CompatUtils.getField( SuggestionsInfo.class, "RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS"); private static final Integer OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = (Integer) CompatUtils - .getFieldValue(null, null, FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS);; + .getFieldValue(null, null, FIELD_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS); private static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS != null ? OBJ_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS : 0; @@ -34,7 +34,7 @@ public class SuggestionsInfoCompatUtils { /** * Returns the flag value of the attributes of the suggestions that can be obtained by - * {@link #getSuggestionsAttributes}: this tells that the text service thinks + * {@link SuggestionsInfo#getSuggestionsAttributes()}: this tells that the text service thinks * the result suggestions include highly recommended ones. */ public static int getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS() { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java index 16b4eafc9..275aacf36 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java @@ -42,13 +42,13 @@ public interface KeyboardActionListener { * * @param primaryCode this is the code of the key that was pressed * @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by - * {@link PointerTracker#onTouchEvent} or so, the value should be - * {@link #NOT_A_TOUCH_COORDINATE}. If it's called on insertion from the suggestion - * strip, it should be {@link #SUGGESTION_STRIP_COORDINATE}. + * {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}. + * If it's called on insertion from the suggestion strip, it should be + * {@link #SUGGESTION_STRIP_COORDINATE}. * @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by - * {@link PointerTracker#onTouchEvent} or so, the value should be - * {@link #NOT_A_TOUCH_COORDINATE}. If it's called on insertion from the suggestion - * strip, it should be {@link #SUGGESTION_STRIP_COORDINATE}. + * {@link PointerTracker} or so, the value should be {@link #NOT_A_TOUCH_COORDINATE}. + * If it's called on insertion from the suggestion strip, it should be + * {@link #SUGGESTION_STRIP_COORDINATE}. */ public void onCodeInput(int primaryCode, int x, int y); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 8ec440500..311d3dc9d 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -252,7 +252,7 @@ public class BinaryDictionaryFileDumper { * also apply. * * @param input the stream to be copied. - * @param outputFile an outputstream to copy the data to. + * @param output an output stream to copy the data to. */ private static void checkMagicAndCopyFileTo(final BufferedInputStream input, final FileOutputStream output) throws FileNotFoundException, IOException { diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 79bf33850..9d26a2343 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -38,7 +38,7 @@ public abstract class Dictionary { /** * Interface to be implemented by classes requesting words to be fetched from the dictionary. - * @see #getWords(WordComposer, WordCallback) + * @see #getWords(WordComposer, WordCallback, ProximityInfo) */ public interface WordCallback { /** diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 86c153958..9f9f6b87d 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -68,7 +68,6 @@ import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.LatinKeyboardView; -import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.suggestions.SuggestionsView; diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java index 732efadd6..dc0868e7c 100644 --- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java +++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java @@ -16,7 +16,6 @@ package com.android.inputmethod.latin; -import android.content.Context; import android.content.SharedPreferences; import android.view.inputmethod.EditorInfo; diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java index 0694ffe77..7f3be8584 100644 --- a/java/src/com/android/inputmethod/latin/ResearchLogger.java +++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java @@ -178,8 +178,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } if (prefs != null) { sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); + prefs.registerOnSharedPreferenceChangeListener(this); } - prefs.registerOnSharedPreferenceChangeListener(this); } /** diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 555a49ef4..bd8532ebd 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -144,9 +144,7 @@ public class WordComposer { } /** - * Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of - * the array containing unicode for adjacent keys, sorted by reducing probability/proximity. - * @param codes the array of unicode values + * Add a new keystroke, with the pressed key's code point with the touch point coordinates. */ private void add(int primaryCode, int keyX, int keyY) { final int newIndex = size(); -- cgit v1.2.3-83-g751a From cec8552b18fd74517512a43a8d75f64e64bd12c3 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 11 Apr 2012 21:29:53 +0900 Subject: Pass a parameter to the dict pack if we don't have a default dict Also, optimize quite a bit the code that decides whether we have a default dict or not. Bug: 5705834 Change-Id: Ied20fbcbbc42cbe8c01759d11b1804d1156c6960 --- .../latin/BinaryDictionaryFileDumper.java | 27 +++++++--- .../inputmethod/latin/BinaryDictionaryGetter.java | 4 +- .../inputmethod/latin/DictionaryFactory.java | 62 +++++++--------------- 3 files changed, 42 insertions(+), 51 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 311d3dc9d..e4d081b56 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -53,17 +53,23 @@ public class BinaryDictionaryFileDumper { 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"; + // Prevents this class to be accidentally instantiated. private BinaryDictionaryFileDumper() { } /** - * Return for a given locale or dictionary id the provider URI to get the dictionary. + * 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 Uri getProviderUri(String path) { + private static Uri.Builder getProviderUriBuilder(final String path) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath( - path).build(); + path); } /** @@ -71,9 +77,13 @@ public class BinaryDictionaryFileDumper { * available to copy into Latin IME. */ private static List getWordListWordListInfos(final Locale locale, - final Context context) { + final Context context, final boolean hasDefaultWordList) { final ContentResolver resolver = context.getContentResolver(); - final Uri dictionaryPackUri = getProviderUri(locale.toString()); + 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.emptyList(); @@ -132,7 +142,7 @@ public class BinaryDictionaryFileDumper { final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED; final int MODE_MAX = NONE; - final Uri wordListUri = getProviderUri(id); + final Uri wordListUri = getProviderUriBuilder(id).build(); final String outputFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context); for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) { @@ -231,9 +241,10 @@ public class BinaryDictionaryFileDumper { * @throw IOException if the provider-returned data could not be read. */ public static List cacheWordListsFromContentProvider(final Locale locale, - final Context context) { + final Context context, final boolean hasDefaultWordList) { final ContentResolver resolver = context.getContentResolver(); - final List idList = getWordListWordListInfos(locale, context); + final List idList = getWordListWordListInfos(locale, context, + hasDefaultWordList); final List fileAddressList = new ArrayList(); for (WordListInfo id : idList) { final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index b0c2adc79..072dec9d1 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -263,10 +263,12 @@ class BinaryDictionaryGetter { public static ArrayList getDictionaryFiles(final Locale locale, final Context context) { + final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale); // 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); + BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, + hasDefaultWordList); final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); final String mainDictId = getMainDictId(locale); diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java index bf05f3bc3..17d75368e 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java @@ -94,13 +94,14 @@ public class DictionaryFactory { final Locale locale) { AssetFileDescriptor afd = null; try { - final int resId = getMainDictionaryResourceId(context.getResources(), locale); + final int resId = + getMainDictionaryResourceIdIfAvailableForLocale(context.getResources(), locale); + if (0 == resId) return null; afd = context.getResources().openRawResourceFd(resId); if (afd == null) { Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); return null; } - if (!isFullDictionary(afd)) return null; final String sourceDir = context.getApplicationInfo().sourceDir; final File packagePath = new File(sourceDir); // TODO: Come up with a way to handle a directory. @@ -152,55 +153,19 @@ public class DictionaryFactory { */ public static boolean isDictionaryAvailable(Context context, Locale locale) { final Resources res = context.getResources(); - final int resourceId = getMainDictionaryResourceId(res, locale); - final AssetFileDescriptor afd = res.openRawResourceFd(resourceId); - final boolean hasDictionary = isFullDictionary(afd); - try { - if (null != afd) afd.close(); - } catch (java.io.IOException e) { - /* Um, what can we do here exactly? */ - } - return hasDictionary; - } - - // TODO: Do not use the size of the dictionary as an unique dictionary ID. - public static Long getDictionaryId(final Context context, final Locale locale) { - final Resources res = context.getResources(); - final int resourceId = getMainDictionaryResourceId(res, locale); - final AssetFileDescriptor afd = res.openRawResourceFd(resourceId); - final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH) - ? afd.getLength() - : null; - try { - if (null != afd) afd.close(); - } catch (java.io.IOException e) { - } - return size; - } - - // TODO: Find the Right Way to find out whether the resource is a placeholder or not. - // Suggestion : strip the locale, open the placeholder file and store its offset. - // Upon opening the file, if it's the same offset, then it's the placeholder. - private static final long PLACEHOLDER_LENGTH = 34; - /** - * Finds out whether the data pointed out by an AssetFileDescriptor is a full - * dictionary (as opposed to null, or to a place holder). - * @param afd the file descriptor to test, or null - * @return true if the dictionary is a real full dictionary, false if it's null or a placeholder - */ - protected static boolean isFullDictionary(final AssetFileDescriptor afd) { - return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH); + return 0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale); } private static final String DEFAULT_MAIN_DICT = "main"; private static final String MAIN_DICT_PREFIX = "main_"; /** - * Returns a main dictionary resource id + * Helper method to return a dictionary res id for a locale, or 0 if none. * @param locale dictionary locale * @return main dictionary resource id */ - public static int getMainDictionaryResourceId(Resources res, Locale locale) { + private static int getMainDictionaryResourceIdIfAvailableForLocale(final Resources res, + final Locale locale) { final String packageName = LatinIME.class.getPackage().getName(); int resId; @@ -218,6 +183,19 @@ public class DictionaryFactory { return resId; } + // Not found, return 0 + return 0; + } + + /** + * Returns a main dictionary resource id + * @param locale dictionary locale + * @return main dictionary resource id + */ + public static int getMainDictionaryResourceId(final Resources res, final Locale locale) { + int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale); + if (0 != resourceId) return resourceId; + final String packageName = LatinIME.class.getPackage().getName(); return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", packageName); } } -- cgit v1.2.3-83-g751a From 04b03f4dd63a6cc5ea1b4d6afc93c442b907b282 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 20 Apr 2012 18:56:03 +0900 Subject: Allow using a format version 2 word list in LatinIME Change-Id: I73a4df3a83e49be6e8d3a7d14eb027cfe10f1a23 --- .../latin/BinaryDictionaryFileDumper.java | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index e4d081b56..a4670daf2 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -49,7 +49,10 @@ public class BinaryDictionaryFileDumper { */ 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 = new byte[] { 0x78, (byte)0xB1 }; + 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" }; @@ -268,15 +271,18 @@ public class BinaryDictionaryFileDumper { private static void checkMagicAndCopyFileTo(final BufferedInputStream input, final FileOutputStream output) throws FileNotFoundException, IOException { // Check the magic number - final byte[] magicNumberBuffer = new byte[MAGIC_NUMBER.length]; - final int readMagicNumberSize = input.read(magicNumberBuffer, 0, MAGIC_NUMBER.length); - if (readMagicNumberSize < MAGIC_NUMBER.length) { + 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, magicNumberBuffer)) { - throw new IOException("Wrong magic number for downloaded file"); + 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(MAGIC_NUMBER); + output.write(magicNumberBuffer); // Actually copy the file final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE]; -- cgit v1.2.3-83-g751a From b9e2bce95e955b6393c25226ab62fa44d24b904a Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 23 May 2012 14:41:50 +0900 Subject: Remove an updated dictionary that changed locales When a dictionary changes locale, we need to remove the file that corresponds to the old version. It has a different path than the new one, so we have to search for it explicitly. Bug: 6540631 Change-Id: Ie9d63ba636651fe90f8fbb9627b7265ac7b34ccd --- .../latin/BinaryDictionaryFileDumper.java | 1 + .../inputmethod/latin/BinaryDictionaryGetter.java | 33 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index a4670daf2..d1ad4e170 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -193,6 +193,7 @@ public class BinaryDictionaryFileDumper { if (0 >= resolver.delete(wordListUri, 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) { diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 5acd62904..063243e1b 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -282,6 +282,39 @@ class BinaryDictionaryGetter { return result; } + /** + * Remove all files with the passed id, except the passed file. + * + * If a dictionary with a given ID has a metadata change that causes it to change + * path, we need to remove the old version. The only way to do this is to check all + * installed files for a matching ID in a different directory. + */ + public static void removeFilesWithIdExcept(final Context context, final String id, + final File fileToKeep) { + try { + final File canonicalFileToKeep = fileToKeep.getCanonicalFile(); + final File[] directoryList = getCachedDirectoryList(context); + if (null == directoryList) return; + for (File directory : directoryList) { + // There is one directory per locale. See #getCachedDirectoryList + if (!directory.isDirectory()) continue; + final File[] wordLists = directory.listFiles(); + if (null == wordLists) continue; + for (File wordList : wordLists) { + final String fileId = getWordListIdFromFileName(wordList.getName()); + if (fileId.equals(id)) { + if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) { + wordList.delete(); + } + } + } + } + } catch (java.io.IOException e) { + Log.e(TAG, "IOException trying to cleanup files : " + e); + } + } + + /** * Returns the id associated with the main word list for a specified locale. * -- cgit v1.2.3-83-g751a From 3abac7e28e9fbfc96b146abc0028fb5a2a5973da Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Wed, 23 May 2012 18:46:51 +0900 Subject: Notify the dictionary pack about copy failures Bug: 6532896 Change-Id: I57f7b1a400222c89776f9bff652a7da4470232c5 --- .../inputmethod/latin/BinaryDictionaryFileDumper.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java') diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index d1ad4e170..37eced5d6 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -58,6 +58,9 @@ public class BinaryDictionaryFileDumper { 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() { @@ -145,7 +148,7 @@ public class BinaryDictionaryFileDumper { final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED; final int MODE_MAX = NONE; - final Uri wordListUri = getProviderUriBuilder(id).build(); + final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id); final String outputFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context); for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) { @@ -154,6 +157,7 @@ public class BinaryDictionaryFileDumper { File outputFile = null; FileOutputStream outputStream = null; AssetFileDescriptor afd = null; + final Uri wordListUri = wordListUriBuilder.build(); try { // Open input. afd = openAssetFileDescriptor(resolver, wordListUri); @@ -190,7 +194,9 @@ public class BinaryDictionaryFileDumper { break; } checkMagicAndCopyFileTo(new BufferedInputStream(inputStream), outputStream); - if (0 >= resolver.delete(wordListUri, null, null)) { + 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); @@ -226,9 +232,11 @@ public class BinaryDictionaryFileDumper { // 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)) { + // 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; -- cgit v1.2.3-83-g751a