BinaryDictionary.java revision 43cf9076b2d053c554941e55f6073b8f586c510b
1/* 2 * Copyright (C) 2008 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 17package com.android.inputmethod.latin; 18 19import android.text.TextUtils; 20import android.util.SparseArray; 21 22import com.android.inputmethod.annotations.UsedForTesting; 23import com.android.inputmethod.keyboard.ProximityInfo; 24import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 25import com.android.inputmethod.latin.makedict.DictionaryHeader; 26import com.android.inputmethod.latin.makedict.FormatSpec; 27import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; 28import com.android.inputmethod.latin.makedict.UnsupportedFormatException; 29import com.android.inputmethod.latin.settings.NativeSuggestOptions; 30import com.android.inputmethod.latin.utils.CollectionUtils; 31import com.android.inputmethod.latin.utils.JniUtils; 32import com.android.inputmethod.latin.utils.LanguageModelParam; 33import com.android.inputmethod.latin.utils.StringUtils; 34import com.android.inputmethod.latin.utils.WordProperty; 35 36import java.io.File; 37import java.util.ArrayList; 38import java.util.Arrays; 39import java.util.HashMap; 40import java.util.Locale; 41import java.util.Map; 42 43/** 44 * Implements a static, compacted, binary dictionary of standard words. 45 */ 46// TODO: All methods which should be locked need to have a suffix "Locked". 47public final class BinaryDictionary extends Dictionary { 48 private static final String TAG = BinaryDictionary.class.getSimpleName(); 49 50 // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h 51 private static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH; 52 // Must be equal to MAX_RESULTS in native/jni/src/defines.h 53 private static final int MAX_RESULTS = 18; 54 // The cutoff returned by native for auto-commit confidence. 55 // Must be equal to CONFIDENCE_TO_AUTO_COMMIT in native/jni/src/defines.h 56 private static final int CONFIDENCE_TO_AUTO_COMMIT = 1000000; 57 58 @UsedForTesting 59 public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT"; 60 @UsedForTesting 61 public static final String BIGRAM_COUNT_QUERY = "BIGRAM_COUNT"; 62 @UsedForTesting 63 public static final String MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT"; 64 @UsedForTesting 65 public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT"; 66 67 public static final int NOT_A_VALID_TIMESTAMP = -1; 68 69 // Format to get unigram flags from native side via getWordPropertyNative(). 70 private static final int FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT = 4; 71 private static final int FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX = 0; 72 private static final int FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX = 1; 73 private static final int FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX = 2; 74 private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3; 75 76 // Format to get probability and historical info from native side via getWordPropertyNative(). 77 public static final int FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT = 4; 78 public static final int FORMAT_WORD_PROPERTY_PROBABILITY_INDEX = 0; 79 public static final int FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX = 1; 80 public static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 2; 81 public static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 3; 82 83 private long mNativeDict; 84 private final Locale mLocale; 85 private final long mDictSize; 86 private final String mDictFilePath; 87 private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH]; 88 private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS]; 89 private final int[] mSpaceIndices = new int[MAX_RESULTS]; 90 private final int[] mOutputScores = new int[MAX_RESULTS]; 91 private final int[] mOutputTypes = new int[MAX_RESULTS]; 92 // Only one result is ever used 93 private final int[] mOutputAutoCommitFirstWordConfidence = new int[1]; 94 95 private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions(); 96 97 private final SparseArray<DicTraverseSession> mDicTraverseSessions = 98 CollectionUtils.newSparseArray(); 99 100 // TODO: There should be a way to remove used DicTraverseSession objects from 101 // {@code mDicTraverseSessions}. 102 private DicTraverseSession getTraverseSession(final int traverseSessionId) { 103 synchronized(mDicTraverseSessions) { 104 DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId); 105 if (traverseSession == null) { 106 traverseSession = mDicTraverseSessions.get(traverseSessionId); 107 if (traverseSession == null) { 108 traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize); 109 mDicTraverseSessions.put(traverseSessionId, traverseSession); 110 } 111 } 112 return traverseSession; 113 } 114 } 115 116 /** 117 * Constructor for the binary dictionary. This is supposed to be called from the 118 * dictionary factory. 119 * @param filename the name of the file to read through native code. 120 * @param offset the offset of the dictionary data within the file. 121 * @param length the length of the binary data. 122 * @param useFullEditDistance whether to use the full edit distance in suggestions 123 * @param dictType the dictionary type, as a human-readable string 124 * @param isUpdatable whether to open the dictionary file in writable mode. 125 */ 126 public BinaryDictionary(final String filename, final long offset, final long length, 127 final boolean useFullEditDistance, final Locale locale, final String dictType, 128 final boolean isUpdatable) { 129 super(dictType); 130 mLocale = locale; 131 mDictSize = length; 132 mDictFilePath = filename; 133 mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance); 134 loadDictionary(filename, offset, length, isUpdatable); 135 } 136 137 static { 138 JniUtils.loadNativeLibrary(); 139 } 140 141 private static native boolean createEmptyDictFileNative(String filePath, long dictVersion, 142 String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray); 143 private static native long openNative(String sourceDir, long dictOffset, long dictSize, 144 boolean isUpdatable); 145 private static native void getHeaderInfoNative(long dict, int[] outHeaderSize, 146 int[] outFormatVersion, ArrayList<int[]> outAttributeKeys, 147 ArrayList<int[]> outAttributeValues); 148 private static native void flushNative(long dict, String filePath); 149 private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC); 150 private static native void flushWithGCNative(long dict, String filePath); 151 private static native void closeNative(long dict); 152 private static native int getFormatVersionNative(long dict); 153 private static native int getProbabilityNative(long dict, int[] word); 154 private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1); 155 private static native void getWordPropertyNative(long dict, int[] word, 156 int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo, 157 ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo, 158 ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities); 159 private static native int getNextWordNative(long dict, int token, int[] outCodePoints); 160 private static native int getSuggestionsNative(long dict, long proximityInfo, 161 long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, 162 int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint, 163 int[] suggestOptions, int[] prevWordCodePointArray, 164 int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes, 165 int[] outputAutoCommitFirstWordConfidence); 166 private static native float calcNormalizedScoreNative(int[] before, int[] after, int score); 167 private static native int editDistanceNative(int[] before, int[] after); 168 private static native void addUnigramWordNative(long dict, int[] word, int probability, 169 int[] shortcutTarget, int shortcutProbability, boolean isNotAWord, 170 boolean isBlacklisted, int timestamp); 171 private static native void addBigramWordsNative(long dict, int[] word0, int[] word1, 172 int probability, int timestamp); 173 private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1); 174 private static native int addMultipleDictionaryEntriesNative(long dict, 175 LanguageModelParam[] languageModelParams, int startIndex); 176 private static native int calculateProbabilityNative(long dict, int unigramProbability, 177 int bigramProbability); 178 private static native int setCurrentTimeForTestNative(int currentTime); 179 private static native String getPropertyNative(long dict, String query); 180 181 public static boolean createEmptyDictFile(final String filePath, final long dictVersion, 182 final Locale locale, final Map<String, String> attributeMap) { 183 final String[] keyArray = new String[attributeMap.size()]; 184 final String[] valueArray = new String[attributeMap.size()]; 185 int index = 0; 186 for (final String key : attributeMap.keySet()) { 187 keyArray[index] = key; 188 valueArray[index] = attributeMap.get(key); 189 index++; 190 } 191 return createEmptyDictFileNative(filePath, dictVersion, locale.toString(), keyArray, 192 valueArray); 193 } 194 195 // TODO: Move native dict into session 196 private final void loadDictionary(final String path, final long startOffset, 197 final long length, final boolean isUpdatable) { 198 mNativeDict = openNative(path, startOffset, length, isUpdatable); 199 } 200 201 @UsedForTesting 202 public DictionaryHeader getHeader() throws UnsupportedFormatException { 203 if (mNativeDict == 0) { 204 return null; 205 } 206 final int[] outHeaderSize = new int[1]; 207 final int[] outFormatVersion = new int[1]; 208 final ArrayList<int[]> outAttributeKeys = CollectionUtils.newArrayList(); 209 final ArrayList<int[]> outAttributeValues = CollectionUtils.newArrayList(); 210 getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys, 211 outAttributeValues); 212 final HashMap<String, String> attributes = new HashMap<String, String>(); 213 for (int i = 0; i < outAttributeKeys.size(); i++) { 214 final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray( 215 outAttributeKeys.get(i)); 216 final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray( 217 outAttributeValues.get(i)); 218 attributes.put(attributeKey, attributeValue); 219 } 220 final boolean hasHistoricalInfo = 221 attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY).equals( 222 DictionaryHeader.ATTRIBUTE_VALUE_TRUE); 223 return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes), 224 new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo)); 225 } 226 227 228 @Override 229 public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, 230 final String prevWord, final ProximityInfo proximityInfo, 231 final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) { 232 return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords, 233 additionalFeaturesOptions, 0 /* sessionId */); 234 } 235 236 @Override 237 public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer, 238 final String prevWord, final ProximityInfo proximityInfo, 239 final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, 240 final int sessionId) { 241 if (!isValidDictionary()) return null; 242 243 Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE); 244 // TODO: toLowerCase in the native code 245 final int[] prevWordCodePointArray = (null == prevWord) 246 ? null : StringUtils.toCodePointArray(prevWord); 247 final int composerSize = composer.size(); 248 249 final boolean isGesture = composer.isBatchMode(); 250 if (composerSize <= 1 || !isGesture) { 251 if (composerSize > MAX_WORD_LENGTH - 1) return null; 252 for (int i = 0; i < composerSize; i++) { 253 mInputCodePoints[i] = composer.getCodeAt(i); 254 } 255 } 256 257 final InputPointers ips = composer.getInputPointers(); 258 final int inputSize = isGesture ? ips.getPointerSize() : composerSize; 259 mNativeSuggestOptions.setIsGesture(isGesture); 260 mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions); 261 // proximityInfo and/or prevWordForBigrams may not be null. 262 final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(), 263 getTraverseSession(sessionId).getSession(), ips.getXCoordinates(), 264 ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints, 265 inputSize, 0 /* commitPoint */, mNativeSuggestOptions.getOptions(), 266 prevWordCodePointArray, mOutputCodePoints, mOutputScores, mSpaceIndices, 267 mOutputTypes, mOutputAutoCommitFirstWordConfidence); 268 final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); 269 for (int j = 0; j < count; ++j) { 270 final int start = j * MAX_WORD_LENGTH; 271 int len = 0; 272 while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) { 273 ++len; 274 } 275 if (len > 0) { 276 final int flags = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_FLAGS; 277 if (blockOffensiveWords 278 && 0 != (flags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE) 279 && 0 == (flags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH)) { 280 // If we block potentially offensive words, and if the word is possibly 281 // offensive, then we don't output it unless it's also an exact match. 282 continue; 283 } 284 final int kind = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_KIND; 285 final int score = SuggestedWordInfo.KIND_WHITELIST == kind 286 ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j]; 287 // TODO: check that all users of the `kind' parameter are ready to accept 288 // flags too and pass mOutputTypes[j] instead of kind 289 suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len), 290 score, kind, this /* sourceDict */, 291 mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */, 292 mOutputAutoCommitFirstWordConfidence[0])); 293 } 294 } 295 return suggestions; 296 } 297 298 public boolean isValidDictionary() { 299 return mNativeDict != 0; 300 } 301 302 public int getFormatVersion() { 303 return getFormatVersionNative(mNativeDict); 304 } 305 306 public static float calcNormalizedScore(final String before, final String after, 307 final int score) { 308 return calcNormalizedScoreNative(StringUtils.toCodePointArray(before), 309 StringUtils.toCodePointArray(after), score); 310 } 311 312 public static int editDistance(final String before, final String after) { 313 if (before == null || after == null) { 314 throw new IllegalArgumentException(); 315 } 316 return editDistanceNative(StringUtils.toCodePointArray(before), 317 StringUtils.toCodePointArray(after)); 318 } 319 320 @Override 321 public boolean isValidWord(final String word) { 322 return getFrequency(word) != NOT_A_PROBABILITY; 323 } 324 325 @Override 326 public int getFrequency(final String word) { 327 if (word == null) return NOT_A_PROBABILITY; 328 int[] codePoints = StringUtils.toCodePointArray(word); 329 return getProbabilityNative(mNativeDict, codePoints); 330 } 331 332 // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni 333 // calls when checking for changes in an entire dictionary. 334 public boolean isValidBigram(final String word0, final String word1) { 335 return getBigramProbability(word0, word1) != NOT_A_PROBABILITY; 336 } 337 338 public int getBigramProbability(final String word0, final String word1) { 339 if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) return NOT_A_PROBABILITY; 340 final int[] codePoints0 = StringUtils.toCodePointArray(word0); 341 final int[] codePoints1 = StringUtils.toCodePointArray(word1); 342 return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1); 343 } 344 345 public WordProperty getWordProperty(final String word) { 346 if (TextUtils.isEmpty(word)) { 347 return null; 348 } 349 final int[] codePoints = StringUtils.toCodePointArray(word); 350 final int[] outCodePoints = new int[MAX_WORD_LENGTH]; 351 final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT]; 352 final int[] outProbabilityInfo = 353 new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT]; 354 final ArrayList<int[]> outBigramTargets = CollectionUtils.newArrayList(); 355 final ArrayList<int[]> outBigramProbabilityInfo = CollectionUtils.newArrayList(); 356 final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList(); 357 final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList(); 358 getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo, 359 outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, 360 outShortcutProbabilities); 361 return new WordProperty(codePoints, 362 outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX], 363 outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX], 364 outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX], 365 outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbabilityInfo, 366 outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, 367 outShortcutProbabilities); 368 } 369 370 public static class GetNextWordPropertyResult { 371 public WordProperty mWordProperty; 372 public int mNextToken; 373 374 public GetNextWordPropertyResult(final WordProperty wordPreperty, final int nextToken) { 375 mWordProperty = wordPreperty; 376 mNextToken = nextToken; 377 } 378 } 379 380 /** 381 * Method to iterate all words in the dictionary for makedict. 382 * If token is 0, this method newly starts iterating the dictionary. 383 */ 384 public GetNextWordPropertyResult getNextWordProperty(final int token) { 385 final int[] codePoints = new int[MAX_WORD_LENGTH]; 386 final int nextToken = getNextWordNative(mNativeDict, token, codePoints); 387 final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints); 388 return new GetNextWordPropertyResult(getWordProperty(word), nextToken); 389 } 390 391 // Add a unigram entry to binary dictionary with unigram attributes in native code. 392 public void addUnigramWord(final String word, final int probability, 393 final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord, 394 final boolean isBlacklisted, final int timestamp) { 395 if (TextUtils.isEmpty(word)) { 396 return; 397 } 398 final int[] codePoints = StringUtils.toCodePointArray(word); 399 final int[] shortcutTargetCodePoints = (shortcutTarget != null) ? 400 StringUtils.toCodePointArray(shortcutTarget) : null; 401 addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints, 402 shortcutProbability, isNotAWord, isBlacklisted, timestamp); 403 } 404 405 // Add a bigram entry to binary dictionary with timestamp in native code. 406 public void addBigramWords(final String word0, final String word1, final int probability, 407 final int timestamp) { 408 if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) { 409 return; 410 } 411 final int[] codePoints0 = StringUtils.toCodePointArray(word0); 412 final int[] codePoints1 = StringUtils.toCodePointArray(word1); 413 addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp); 414 } 415 416 // Remove a bigram entry form binary dictionary in native code. 417 public void removeBigramWords(final String word0, final String word1) { 418 if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) { 419 return; 420 } 421 final int[] codePoints0 = StringUtils.toCodePointArray(word0); 422 final int[] codePoints1 = StringUtils.toCodePointArray(word1); 423 removeBigramWordsNative(mNativeDict, codePoints0, codePoints1); 424 } 425 426 public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) { 427 if (!isValidDictionary()) return; 428 int processedParamCount = 0; 429 while (processedParamCount < languageModelParams.length) { 430 if (needsToRunGC(true /* mindsBlockByGC */)) { 431 flushWithGC(); 432 } 433 processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict, 434 languageModelParams, processedParamCount); 435 if (processedParamCount <= 0) { 436 return; 437 } 438 } 439 } 440 441 private void reopen() { 442 close(); 443 final File dictFile = new File(mDictFilePath); 444 // WARNING: Because we pass 0 as the offset and file.length() as the length, this can 445 // only be called for actual files. Right now it's only called by the flush() family of 446 // functions, which require an updatable dictionary, so it's okay. But beware. 447 loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */, 448 dictFile.length(), true /* isUpdatable */); 449 } 450 451 public void flush() { 452 if (!isValidDictionary()) return; 453 flushNative(mNativeDict, mDictFilePath); 454 reopen(); 455 } 456 457 public void flushWithGC() { 458 if (!isValidDictionary()) return; 459 flushWithGCNative(mNativeDict, mDictFilePath); 460 reopen(); 461 } 462 463 /** 464 * Checks whether GC is needed to run or not. 465 * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about 466 * the blocking in some situations such as in idle time or just before closing. 467 * @return whether GC is needed to run or not. 468 */ 469 public boolean needsToRunGC(final boolean mindsBlockByGC) { 470 if (!isValidDictionary()) return false; 471 return needsToRunGCNative(mNativeDict, mindsBlockByGC); 472 } 473 474 @UsedForTesting 475 public int calculateProbability(final int unigramProbability, final int bigramProbability) { 476 if (!isValidDictionary()) return NOT_A_PROBABILITY; 477 return calculateProbabilityNative(mNativeDict, unigramProbability, bigramProbability); 478 } 479 480 /** 481 * Control the current time to be used in the native code. If currentTime >= 0, this method sets 482 * the current time and gets into test mode. 483 * In test mode, set timestamp is used as the current time in the native code. 484 * If currentTime < 0, quit the test mode and returns to using time() to get the current time. 485 * 486 * @param currentTime seconds since the unix epoch 487 * @return current time got in the native code. 488 */ 489 @UsedForTesting 490 public static int setCurrentTimeForTest(final int currentTime) { 491 return setCurrentTimeForTestNative(currentTime); 492 } 493 494 @UsedForTesting 495 public String getPropertyForTest(final String query) { 496 if (!isValidDictionary()) return ""; 497 return getPropertyNative(mNativeDict, query); 498 } 499 500 @Override 501 public boolean shouldAutoCommit(final SuggestedWordInfo candidate) { 502 return candidate.mAutoCommitFirstWordConfidence > CONFIDENCE_TO_AUTO_COMMIT; 503 } 504 505 @Override 506 public void close() { 507 synchronized (mDicTraverseSessions) { 508 final int sessionsSize = mDicTraverseSessions.size(); 509 for (int index = 0; index < sessionsSize; ++index) { 510 final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index); 511 if (traverseSession != null) { 512 traverseSession.close(); 513 } 514 } 515 mDicTraverseSessions.clear(); 516 } 517 closeInternalLocked(); 518 } 519 520 private synchronized void closeInternalLocked() { 521 if (mNativeDict != 0) { 522 closeNative(mNativeDict); 523 mNativeDict = 0; 524 } 525 } 526 527 // TODO: Manage BinaryDictionary instances without using WeakReference or something. 528 @Override 529 protected void finalize() throws Throwable { 530 try { 531 closeInternalLocked(); 532 } finally { 533 super.finalize(); 534 } 535 } 536} 537