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