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