BinaryDictionary.java revision c899038eee5c01d520a2707cca01ee093a674d05
1/* 2 * Copyright (C) 2008 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 com.android.inputmethod.keyboard.Keyboard; 20import com.android.inputmethod.keyboard.KeyboardSwitcher; 21import com.android.inputmethod.keyboard.ProximityInfo; 22 23import android.content.Context; 24import android.content.res.AssetFileDescriptor; 25import android.util.Log; 26 27import java.io.File; 28import java.util.Arrays; 29import java.util.Locale; 30 31/** 32 * Implements a static, compacted, binary dictionary of standard words. 33 */ 34public class BinaryDictionary extends Dictionary { 35 36 public static final String DICTIONARY_PACK_AUTHORITY = 37 "com.android.inputmethod.latin.dictionarypack"; 38 39 /** 40 * There is a difference between what java and native code can handle. 41 * This value should only be used in BinaryDictionary.java 42 * It is necessary to keep it at this value because some languages e.g. German have 43 * really long words. 44 */ 45 public static final int MAX_WORD_LENGTH = 48; 46 public static final int MAX_WORDS = 18; 47 48 private static final String TAG = "BinaryDictionary"; 49 private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; 50 private static final int MAX_BIGRAMS = 60; 51 52 private static final int TYPED_LETTER_MULTIPLIER = 2; 53 54 private static final BinaryDictionary sInstance = new BinaryDictionary(); 55 private int mDicTypeId; 56 private int mNativeDict; 57 private long mDictLength; 58 private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE]; 59 private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; 60 private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; 61 private final int[] mScores = new int[MAX_WORDS]; 62 private final int[] mBigramScores = new int[MAX_BIGRAMS]; 63 64 private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance(); 65 66 public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING = 67 new Flag(R.bool.config_require_umlaut_processing, 0x1); 68 69 // Can create a new flag from extravalue : 70 // public static final Flag FLAG_MYFLAG = 71 // new Flag("my_flag", 0x02); 72 73 private static final Flag[] ALL_FLAGS = { 74 // Here should reside all flags that trigger some special processing 75 // These *must* match the definition in UnigramDictionary enum in 76 // unigram_dictionary.h so please update both at the same time. 77 FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING, 78 }; 79 80 private int mFlags = 0; 81 82 private BinaryDictionary() { 83 } 84 85 /** 86 * Initializes a dictionary from a raw resource file 87 * @param context application context for reading resources 88 * @param resId the resource containing the raw binary dictionary 89 * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_* 90 * @return an initialized instance of BinaryDictionary 91 */ 92 public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) { 93 synchronized (sInstance) { 94 sInstance.closeInternal(); 95 try { 96 final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); 97 if (afd == null) { 98 Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); 99 return null; 100 } 101 final String sourceDir = context.getApplicationInfo().sourceDir; 102 final File packagePath = new File(sourceDir); 103 // TODO: Come up with a way to handle a directory. 104 if (!packagePath.isFile()) { 105 Log.e(TAG, "sourceDir is not a file: " + sourceDir); 106 return null; 107 } 108 sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength()); 109 sInstance.mDicTypeId = dicTypeId; 110 } catch (android.content.res.Resources.NotFoundException e) { 111 Log.e(TAG, "Could not find the resource. resId=" + resId); 112 return null; 113 } 114 } 115 sInstance.mFlags = Flag.initFlags(ALL_FLAGS, context, SubtypeSwitcher.getInstance()); 116 return sInstance; 117 } 118 119 /* package for test */ static BinaryDictionary initDictionary(Context context, File dictionary, 120 long startOffset, long length, int dicTypeId, Flag[] flagArray) { 121 synchronized (sInstance) { 122 sInstance.closeInternal(); 123 if (dictionary.isFile()) { 124 sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length); 125 sInstance.mDicTypeId = dicTypeId; 126 } else { 127 Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath()); 128 return null; 129 } 130 } 131 sInstance.mFlags = Flag.initFlags(flagArray, context, null); 132 return sInstance; 133 } 134 135 static { 136 Utils.loadNativeLibrary(); 137 } 138 139 /** 140 * Initializes a dictionary from a dictionary pack. 141 * 142 * This searches for a content provider providing a dictionary pack for the specified 143 * locale. If none is found, it falls back to using the resource passed as fallBackResId 144 * as a dictionary. 145 * @param context application context for reading resources 146 * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_* 147 * @param locale the locale for which to create the dictionary 148 * @param fallBackResId the id of the resource to use as a fallback if no pack is found 149 * @return an initialized instance of BinaryDictionary 150 */ 151 public static BinaryDictionary initDictionaryFromManager(Context context, int dicTypeId, 152 Locale locale, int fallbackResId) { 153 if (null == locale) { 154 Log.e(TAG, "No locale defined for dictionary"); 155 return initDictionary(context, fallbackResId, dicTypeId); 156 } 157 synchronized (sInstance) { 158 sInstance.closeInternal(); 159 160 final AssetFileAddress dictFile = BinaryDictionaryGetter.getDictionaryFile(locale, 161 context, fallbackResId); 162 if (null != dictFile) { 163 sInstance.loadDictionary(dictFile.mFilename, dictFile.mOffset, dictFile.mLength); 164 sInstance.mDicTypeId = dicTypeId; 165 } 166 } 167 sInstance.mFlags = Flag.initFlags(ALL_FLAGS, context, SubtypeSwitcher.getInstance()); 168 return sInstance; 169 } 170 171 private native int openNative(String sourceDir, long dictOffset, long dictSize, 172 int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, 173 int maxWords, int maxAlternatives); 174 private native void closeNative(int dict); 175 private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); 176 private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates, 177 int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars, 178 int[] scores); 179 private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength, 180 int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores, 181 int maxWordLength, int maxBigrams, int maxAlternatives); 182 183 private final void loadDictionary(String path, long startOffset, long length) { 184 mNativeDict = openNative(path, startOffset, length, 185 TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER, 186 MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE); 187 mDictLength = length; 188 } 189 190 @Override 191 public void getBigrams(final WordComposer codes, final CharSequence previousWord, 192 final WordCallback callback) { 193 if (mNativeDict == 0) return; 194 195 char[] chars = previousWord.toString().toCharArray(); 196 Arrays.fill(mOutputChars_bigrams, (char) 0); 197 Arrays.fill(mBigramScores, 0); 198 199 int codesSize = codes.size(); 200 Arrays.fill(mInputCodes, -1); 201 int[] alternatives = codes.getCodesAt(0); 202 System.arraycopy(alternatives, 0, mInputCodes, 0, 203 Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); 204 205 int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize, 206 mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS, 207 MAX_PROXIMITY_CHARS_SIZE); 208 209 for (int j = 0; j < count; ++j) { 210 if (mBigramScores[j] < 1) break; 211 final int start = j * MAX_WORD_LENGTH; 212 int len = 0; 213 while (len < MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) { 214 ++len; 215 } 216 if (len > 0) { 217 callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j], 218 mDicTypeId, DataType.BIGRAM); 219 } 220 } 221 } 222 223 @Override 224 public void getWords(final WordComposer codes, final WordCallback callback) { 225 final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(), 226 mOutputChars, mScores); 227 228 for (int j = 0; j < count; ++j) { 229 if (mScores[j] < 1) break; 230 final int start = j * MAX_WORD_LENGTH; 231 int len = 0; 232 while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { 233 ++len; 234 } 235 if (len > 0) { 236 callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId, 237 DataType.UNIGRAM); 238 } 239 } 240 } 241 242 /* package for test */ boolean isValidDictionary() { 243 return mNativeDict != 0; 244 } 245 246 /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard, 247 char[] outputChars, int[] scores) { 248 if (!isValidDictionary()) return -1; 249 250 final int codesSize = codes.size(); 251 // Won't deal with really long words. 252 if (codesSize > MAX_WORD_LENGTH - 1) return -1; 253 254 Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE); 255 for (int i = 0; i < codesSize; i++) { 256 int[] alternatives = codes.getCodesAt(i); 257 System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE, 258 Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); 259 } 260 Arrays.fill(outputChars, (char) 0); 261 Arrays.fill(scores, 0); 262 263 return getSuggestionsNative( 264 mNativeDict, keyboard.getProximityInfo(), 265 codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, 266 mFlags, outputChars, scores); 267 } 268 269 @Override 270 public boolean isValidWord(CharSequence word) { 271 if (word == null) return false; 272 char[] chars = word.toString().toCharArray(); 273 return isValidWordNative(mNativeDict, chars, chars.length); 274 } 275 276 public long getSize() { 277 return mDictLength; // This value is initialized in loadDictionary() 278 } 279 280 @Override 281 public synchronized void close() { 282 closeInternal(); 283 } 284 285 private void closeInternal() { 286 if (mNativeDict != 0) { 287 closeNative(mNativeDict); 288 mNativeDict = 0; 289 mDictLength = 0; 290 } 291 } 292 293 @Override 294 protected void finalize() throws Throwable { 295 try { 296 closeInternal(); 297 } finally { 298 super.finalize(); 299 } 300 } 301} 302