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