diff options
Diffstat (limited to 'java/src/org/kelar/inputmethod/latin/DictionaryFactory.java')
-rw-r--r-- | java/src/org/kelar/inputmethod/latin/DictionaryFactory.java | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/java/src/org/kelar/inputmethod/latin/DictionaryFactory.java b/java/src/org/kelar/inputmethod/latin/DictionaryFactory.java new file mode 100644 index 000000000..cb5378aef --- /dev/null +++ b/java/src/org/kelar/inputmethod/latin/DictionaryFactory.java @@ -0,0 +1,161 @@ +/* + * 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 org.kelar.inputmethod.latin; + +import android.content.ContentProviderClient; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.util.Log; + +import org.kelar.inputmethod.latin.utils.DictionaryInfoUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Locale; + +/** + * Factory for dictionary instances. + */ +public final class DictionaryFactory { + private static final String TAG = DictionaryFactory.class.getSimpleName(); + + /** + * Initializes a main dictionary collection from a dictionary pack, with explicit flags. + * + * This searches for a content provider providing a dictionary pack for the specified + * locale. If none is found, it falls back to the built-in dictionary - if any. + * @param context application context for reading resources + * @param locale the locale for which to create the dictionary + * @return an initialized instance of DictionaryCollection + */ + public static DictionaryCollection createMainDictionaryFromManager(final Context context, + final Locale locale) { + if (null == locale) { + Log.e(TAG, "No locale defined for dictionary"); + return new DictionaryCollection(Dictionary.TYPE_MAIN, locale, + createReadOnlyBinaryDictionary(context, locale)); + } + + final LinkedList<Dictionary> dictList = new LinkedList<>(); + final ArrayList<AssetFileAddress> assetFileList = + BinaryDictionaryGetter.getDictionaryFiles(locale, context, true); + if (null != assetFileList) { + for (final AssetFileAddress f : assetFileList) { + final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = + new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength, + false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN); + if (readOnlyBinaryDictionary.isValidDictionary()) { + dictList.add(readOnlyBinaryDictionary); + } else { + readOnlyBinaryDictionary.close(); + // Prevent this dictionary to do any further harm. + killDictionary(context, f); + } + } + } + + // If the list is empty, that means we should not use any dictionary (for example, the user + // explicitly disabled the main dictionary), so the following is okay. dictList is never + // null, but if for some reason it is, DictionaryCollection handles it gracefully. + return new DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList); + } + + /** + * Kills a dictionary so that it is never used again, if possible. + * @param context The context to contact the dictionary provider, if possible. + * @param f A file address to the dictionary to kill. + */ + public static void killDictionary(final Context context, final AssetFileAddress f) { + if (f.pointsToPhysicalFile()) { + f.deleteUnderlyingFile(); + // Warn the dictionary provider if the dictionary came from there. + final ContentProviderClient providerClient; + try { + providerClient = context.getContentResolver().acquireContentProviderClient( + BinaryDictionaryFileDumper.getProviderUriBuilder("").build()); + } catch (final SecurityException e) { + Log.e(TAG, "No permission to communicate with the dictionary provider", e); + return; + } + if (null == providerClient) { + Log.e(TAG, "Can't establish communication with the dictionary provider"); + return; + } + final String wordlistId = + DictionaryInfoUtils.getWordListIdFromFileName(new File(f.mFilename).getName()); + // TODO: this is a reasonable last resort, but it is suboptimal. + // The following will remove the entry for this dictionary with the dictionary + // provider. When the metadata is downloaded again, we will try downloading it + // again. + // However, in the practice that will mean the user will find themselves without + // the new dictionary. That's fine for languages where it's included in the APK, + // but for other languages it will leave the user without a dictionary at all until + // the next update, which may be a few days away. + // Ideally, we would trigger a new download right away, and use increasing retry + // delays for this particular id/version combination. + // Then again, this is expected to only ever happen in case of human mistake. If + // the wrong file is on the server, the following is still doing the right thing. + // If it's a file left over from the last version however, it's not great. + BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider( + providerClient, + context.getString(R.string.dictionary_pack_client_id), + wordlistId); + } + } + + /** + * Initializes a read-only binary dictionary from a raw resource file + * @param context application context for reading resources + * @param locale the locale to use for the resource + * @return an initialized instance of ReadOnlyBinaryDictionary + */ + private static ReadOnlyBinaryDictionary createReadOnlyBinaryDictionary(final Context context, + final Locale locale) { + AssetFileDescriptor afd = null; + try { + final int resId = DictionaryInfoUtils.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; + } + final String sourceDir = context.getApplicationInfo().sourceDir; + final File packagePath = new File(sourceDir); + // TODO: Come up with a way to handle a directory. + if (!packagePath.isFile()) { + Log.e(TAG, "sourceDir is not a file: " + sourceDir); + return null; + } + return new ReadOnlyBinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(), + false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN); + } catch (android.content.res.Resources.NotFoundException e) { + Log.e(TAG, "Could not find the resource"); + return null; + } finally { + if (null != afd) { + try { + afd.close(); + } catch (java.io.IOException e) { + /* IOException on close ? What am I supposed to do ? */ + } + } + } + } +} |