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