1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.latin; 18 19 import android.content.Context; 20 import android.content.res.AssetFileDescriptor; 21 import android.content.res.Resources; 22 import android.util.Log; 23 24 import com.android.inputmethod.annotations.UsedForTesting; 25 import com.android.inputmethod.latin.utils.CollectionUtils; 26 import com.android.inputmethod.latin.utils.DictionaryInfoUtils; 27 28 import java.io.File; 29 import java.util.ArrayList; 30 import java.util.LinkedList; 31 import java.util.Locale; 32 33 /** 34 * Factory for dictionary instances. 35 */ 36 public final class DictionaryFactory { 37 private static final String TAG = DictionaryFactory.class.getSimpleName(); 38 39 /** 40 * Initializes a main dictionary collection from a dictionary pack, with explicit flags. 41 * 42 * This searches for a content provider providing a dictionary pack for the specified 43 * locale. If none is found, it falls back to the built-in dictionary - if any. 44 * @param context application context for reading resources 45 * @param locale the locale for which to create the dictionary 46 * @param useFullEditDistance whether to use the full edit distance in suggestions 47 * @return an initialized instance of DictionaryCollection 48 */ 49 public static DictionaryCollection createMainDictionaryFromManager(final Context context, 50 final Locale locale, final boolean useFullEditDistance) { 51 if (null == locale) { 52 Log.e(TAG, "No locale defined for dictionary"); 53 return new DictionaryCollection(Dictionary.TYPE_MAIN, 54 createReadOnlyBinaryDictionary(context, locale)); 55 } 56 57 final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList(); 58 final ArrayList<AssetFileAddress> assetFileList = 59 BinaryDictionaryGetter.getDictionaryFiles(locale, context); 60 if (null != assetFileList) { 61 for (final AssetFileAddress f : assetFileList) { 62 final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = 63 new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength, 64 useFullEditDistance, locale, Dictionary.TYPE_MAIN); 65 if (readOnlyBinaryDictionary.isValidDictionary()) { 66 dictList.add(readOnlyBinaryDictionary); 67 } 68 } 69 } 70 71 // If the list is empty, that means we should not use any dictionary (for example, the user 72 // explicitly disabled the main dictionary), so the following is okay. dictList is never 73 // null, but if for some reason it is, DictionaryCollection handles it gracefully. 74 return new DictionaryCollection(Dictionary.TYPE_MAIN, dictList); 75 } 76 77 /** 78 * Initializes a main dictionary collection from a dictionary pack, with default flags. 79 * 80 * This searches for a content provider providing a dictionary pack for the specified 81 * locale. If none is found, it falls back to the built-in dictionary, if any. 82 * @param context application context for reading resources 83 * @param locale the locale for which to create the dictionary 84 * @return an initialized instance of DictionaryCollection 85 */ 86 public static DictionaryCollection createMainDictionaryFromManager(final Context context, 87 final Locale locale) { 88 return createMainDictionaryFromManager(context, locale, false /* useFullEditDistance */); 89 } 90 91 /** 92 * Initializes a read-only binary dictionary from a raw resource file 93 * @param context application context for reading resources 94 * @param locale the locale to use for the resource 95 * @return an initialized instance of ReadOnlyBinaryDictionary 96 */ 97 protected static ReadOnlyBinaryDictionary createReadOnlyBinaryDictionary(final Context context, 98 final Locale locale) { 99 AssetFileDescriptor afd = null; 100 try { 101 final int resId = DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale( 102 context.getResources(), locale); 103 if (0 == resId) return null; 104 afd = context.getResources().openRawResourceFd(resId); 105 if (afd == null) { 106 Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); 107 return null; 108 } 109 final String sourceDir = context.getApplicationInfo().sourceDir; 110 final File packagePath = new File(sourceDir); 111 // TODO: Come up with a way to handle a directory. 112 if (!packagePath.isFile()) { 113 Log.e(TAG, "sourceDir is not a file: " + sourceDir); 114 return null; 115 } 116 return new ReadOnlyBinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(), 117 false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN); 118 } catch (android.content.res.Resources.NotFoundException e) { 119 Log.e(TAG, "Could not find the resource"); 120 return null; 121 } finally { 122 if (null != afd) { 123 try { 124 afd.close(); 125 } catch (java.io.IOException e) { 126 /* IOException on close ? What am I supposed to do ? */ 127 } 128 } 129 } 130 } 131 132 /** 133 * Create a dictionary from passed data. This is intended for unit tests only. 134 * @param dictionaryList the list of files to read, with their offsets and lengths 135 * @param useFullEditDistance whether to use the full edit distance in suggestions 136 * @return the created dictionary, or null. 137 */ 138 @UsedForTesting 139 public static Dictionary createDictionaryForTest(final AssetFileAddress[] dictionaryList, 140 final boolean useFullEditDistance, Locale locale) { 141 final DictionaryCollection dictionaryCollection = 142 new DictionaryCollection(Dictionary.TYPE_MAIN); 143 for (final AssetFileAddress address : dictionaryList) { 144 final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = new ReadOnlyBinaryDictionary( 145 address.mFilename, address.mOffset, address.mLength, useFullEditDistance, 146 locale, Dictionary.TYPE_MAIN); 147 dictionaryCollection.addDictionary(readOnlyBinaryDictionary); 148 } 149 return dictionaryCollection; 150 } 151 152 /** 153 * Find out whether a dictionary is available for this locale. 154 * @param context the context on which to check resources. 155 * @param locale the locale to check for. 156 * @return whether a (non-placeholder) dictionary is available or not. 157 */ 158 public static boolean isDictionaryAvailable(Context context, Locale locale) { 159 final Resources res = context.getResources(); 160 return 0 != DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale( 161 res, locale); 162 } 163 } 164