1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * 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 java.io.File; 25 import java.util.ArrayList; 26 import java.util.LinkedList; 27 import java.util.Locale; 28 29 /** 30 * Factory for dictionary instances. 31 */ 32 public class DictionaryFactory { 33 private static final String TAG = DictionaryFactory.class.getSimpleName(); 34 // This class must be located in the same package as LatinIME.java. 35 private static final String RESOURCE_PACKAGE_NAME = 36 DictionaryFactory.class.getPackage().getName(); 37 38 /** 39 * Initializes a main dictionary collection from a dictionary pack, with explicit flags. 40 * 41 * This searches for a content provider providing a dictionary pack for the specified 42 * locale. If none is found, it falls back to the built-in dictionary - if any. 43 * @param context application context for reading resources 44 * @param locale the locale for which to create the dictionary 45 * @param useFullEditDistance whether to use the full edit distance in suggestions 46 * @return an initialized instance of DictionaryCollection 47 */ createMainDictionaryFromManager(final Context context, final Locale locale, final boolean useFullEditDistance)48 public static DictionaryCollection createMainDictionaryFromManager(final Context context, 49 final Locale locale, final boolean useFullEditDistance) { 50 if (null == locale) { 51 Log.e(TAG, "No locale defined for dictionary"); 52 return new DictionaryCollection(createBinaryDictionary(context, locale)); 53 } 54 55 final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>(); 56 final ArrayList<AssetFileAddress> assetFileList = 57 BinaryDictionaryGetter.getDictionaryFiles(locale, context); 58 if (null != assetFileList) { 59 for (final AssetFileAddress f : assetFileList) { 60 final BinaryDictionary binaryDictionary = 61 new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, 62 useFullEditDistance, locale); 63 if (binaryDictionary.isValidDictionary()) { 64 dictList.add(binaryDictionary); 65 } 66 } 67 } 68 69 // If the list is empty, that means we should not use any dictionary (for example, the user 70 // explicitly disabled the main dictionary), so the following is okay. dictList is never 71 // null, but if for some reason it is, DictionaryCollection handles it gracefully. 72 return new DictionaryCollection(dictList); 73 } 74 75 /** 76 * Initializes a main dictionary collection from a dictionary pack, with default flags. 77 * 78 * This searches for a content provider providing a dictionary pack for the specified 79 * locale. If none is found, it falls back to the built-in dictionary, if any. 80 * @param context application context for reading resources 81 * @param locale the locale for which to create the dictionary 82 * @return an initialized instance of DictionaryCollection 83 */ createMainDictionaryFromManager(final Context context, final Locale locale)84 public static DictionaryCollection createMainDictionaryFromManager(final Context context, 85 final Locale locale) { 86 return createMainDictionaryFromManager(context, locale, false /* useFullEditDistance */); 87 } 88 89 /** 90 * Initializes a dictionary from a raw resource file 91 * @param context application context for reading resources 92 * @param locale the locale to use for the resource 93 * @return an initialized instance of BinaryDictionary 94 */ createBinaryDictionary(final Context context, final Locale locale)95 protected static BinaryDictionary createBinaryDictionary(final Context context, 96 final Locale locale) { 97 AssetFileDescriptor afd = null; 98 try { 99 final int resId = 100 getMainDictionaryResourceIdIfAvailableForLocale(context.getResources(), locale); 101 if (0 == resId) return null; 102 afd = context.getResources().openRawResourceFd(resId); 103 if (afd == null) { 104 Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); 105 return null; 106 } 107 final String sourceDir = context.getApplicationInfo().sourceDir; 108 final File packagePath = new File(sourceDir); 109 // TODO: Come up with a way to handle a directory. 110 if (!packagePath.isFile()) { 111 Log.e(TAG, "sourceDir is not a file: " + sourceDir); 112 return null; 113 } 114 return new BinaryDictionary(context, sourceDir, afd.getStartOffset(), afd.getLength(), 115 false /* useFullEditDistance */, locale); 116 } catch (android.content.res.Resources.NotFoundException e) { 117 Log.e(TAG, "Could not find the resource"); 118 return null; 119 } finally { 120 if (null != afd) { 121 try { 122 afd.close(); 123 } catch (java.io.IOException e) { 124 /* IOException on close ? What am I supposed to do ? */ 125 } 126 } 127 } 128 } 129 130 /** 131 * Create a dictionary from passed data. This is intended for unit tests only. 132 * @param context the test context to create this data from. 133 * @param dictionary the file to read 134 * @param startOffset the offset in the file where the data starts 135 * @param length the length of the data 136 * @param useFullEditDistance whether to use the full edit distance in suggestions 137 * @return the created dictionary, or null. 138 */ createDictionaryForTest(Context context, File dictionary, long startOffset, long length, final boolean useFullEditDistance, Locale locale)139 public static Dictionary createDictionaryForTest(Context context, File dictionary, 140 long startOffset, long length, final boolean useFullEditDistance, Locale locale) { 141 if (dictionary.isFile()) { 142 return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length, 143 useFullEditDistance, locale); 144 } else { 145 Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath()); 146 return null; 147 } 148 } 149 150 /** 151 * Find out whether a dictionary is available for this locale. 152 * @param context the context on which to check resources. 153 * @param locale the locale to check for. 154 * @return whether a (non-placeholder) dictionary is available or not. 155 */ isDictionaryAvailable(Context context, Locale locale)156 public static boolean isDictionaryAvailable(Context context, Locale locale) { 157 final Resources res = context.getResources(); 158 return 0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale); 159 } 160 161 private static final String DEFAULT_MAIN_DICT = "main"; 162 private static final String MAIN_DICT_PREFIX = "main_"; 163 164 /** 165 * Helper method to return a dictionary res id for a locale, or 0 if none. 166 * @param locale dictionary locale 167 * @return main dictionary resource id 168 */ getMainDictionaryResourceIdIfAvailableForLocale(final Resources res, final Locale locale)169 private static int getMainDictionaryResourceIdIfAvailableForLocale(final Resources res, 170 final Locale locale) { 171 int resId; 172 // Try to find main_language_country dictionary. 173 if (!locale.getCountry().isEmpty()) { 174 final String dictLanguageCountry = MAIN_DICT_PREFIX + locale.toString().toLowerCase(); 175 if ((resId = res.getIdentifier( 176 dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) { 177 return resId; 178 } 179 } 180 181 // Try to find main_language dictionary. 182 final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage(); 183 if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) { 184 return resId; 185 } 186 187 // Not found, return 0 188 return 0; 189 } 190 191 /** 192 * Returns a main dictionary resource id 193 * @param locale dictionary locale 194 * @return main dictionary resource id 195 */ getMainDictionaryResourceId(final Resources res, final Locale locale)196 public static int getMainDictionaryResourceId(final Resources res, final Locale locale) { 197 int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale); 198 if (0 != resourceId) return resourceId; 199 return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME); 200 } 201 } 202