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