1ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang/*
2ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang * Copyright (C) 2012 The Android Open Source Project
3ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang *
48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License.
68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at
7ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang *
88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
9ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang *
108aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and
148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under the License.
15ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang */
16ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
17ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyangpackage com.android.inputmethod.latin;
18ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
19ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyangimport android.content.Context;
20ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyangimport android.os.SystemClock;
21ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyangimport android.util.Log;
22ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
23c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagiimport com.android.inputmethod.annotations.UsedForTesting;
24ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyangimport com.android.inputmethod.keyboard.ProximityInfo;
252e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagiimport com.android.inputmethod.latin.makedict.FormatSpec;
2687a72f50c23a4ef357ae623eabc2af16d02466aeKeisuke Kuroyanagiimport com.android.inputmethod.latin.personalization.DynamicPersonalizationDictionaryWriter;
272e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagiimport com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
28ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanadaimport com.android.inputmethod.latin.utils.AsyncResultHolder;
29e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.CollectionUtils;
30ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanadaimport com.android.inputmethod.latin.utils.PrioritizedSerialExecutor;
31ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
32ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyangimport java.io.File;
33f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyangimport java.util.ArrayList;
342e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagiimport java.util.HashMap;
355ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagiimport java.util.Map;
36ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanadaimport java.util.concurrent.ConcurrentHashMap;
37e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagiimport java.util.concurrent.atomic.AtomicBoolean;
386e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagiimport java.util.concurrent.atomic.AtomicReference;
39ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
40ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang/**
41ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang * Abstract base class for an expandable dictionary that can be created and updated dynamically
42ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang * during runtime. When updated it automatically generates a new binary dictionary to handle future
43ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang * queries in native code. This binary dictionary is written to internal storage, and potentially
44ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang * shared across multiple ExpandableBinaryDictionary instances. Updates to each dictionary filename
45ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang * are controlled across multiple instances to ensure that only one instance can update the same
46ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang * dictionary at the same time.
47ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang */
48ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyangabstract public class ExpandableBinaryDictionary extends Dictionary {
49ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
50ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /** Used for Log actions from this class */
51ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    private static final String TAG = ExpandableBinaryDictionary.class.getSimpleName();
52ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
53ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /** Whether to print debug output to log */
54ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    private static boolean DEBUG = false;
55ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
562e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi    // TODO: Remove.
57e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi    /** Whether to call binary dictionary dynamically updating methods. */
582e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi    public static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = true;
59e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi
60ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada    private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
61ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada
62ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
6396b22200beb98fd1a6288f4cf53e38611a09cdd0Ken Wakasa     * The maximum length of a word in this dictionary.
64ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
65ffcbbaf12788a9fc9398607a548e552d7d2bf05eSatoshi Kataoka    protected static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
66ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
675ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi    private static final int DICTIONARY_FORMAT_VERSION = 3;
685ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi
695ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi    private static final String SUPPORTS_DYNAMIC_UPDATE =
705ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi            FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE;
712e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi
72ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
73e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi     * A static map of update controllers, each of which records the time of accesses to a single
74e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi     * binary dictionary file and tracks whether the file is regenerating. The key for this map is
75e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi     * the filename and the value is the shared dictionary time recorder associated with that
76e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi     * filename.
77ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
78e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    private static final ConcurrentHashMap<String, DictionaryUpdateController>
79e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi            sFilenameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
80ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada
81e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
82ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap();
83ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
84ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /** The application context. */
85ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    protected final Context mContext;
86ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
87ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
88ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * The binary dictionary generated dynamically from the fusion dictionary. This is used to
89ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * answer unigram and bigram queries.
90ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
91ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    private BinaryDictionary mBinaryDictionary;
92ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
93e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi    // TODO: Remove and handle dictionaries in native code.
94edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi    /** The in-memory dictionary used to generate the binary dictionary. */
95e5a35711b854aedeeea2f45105b941b9deee49bcSatoshi Kataoka    protected AbstractDictionaryWriter mDictionaryWriter;
96ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
97ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
98ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
99ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * dictionary instances with the same filename is supported, with access controlled by
100ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada     * DictionaryTimeRecorder.
101ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
102ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    private final String mFilename;
103ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
104c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    /** Whether to support dynamically updating the dictionary */
105c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    private final boolean mIsUpdatable;
106c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi
10737e0fd2ff04a2a87e421abea8bc407bd312dbfc6Keisuke Kuroyanagi    // TODO: remove, once dynamic operations is serialized
108e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    /** Controls updating the shared binary dictionary file across multiple instances. */
109e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    private final DictionaryUpdateController mFilenameDictionaryUpdateController;
110ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
11137e0fd2ff04a2a87e421abea8bc407bd312dbfc6Keisuke Kuroyanagi    // TODO: remove, once dynamic operations is serialized
112e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    /** Controls updating the local binary dictionary for this instance. */
113e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    private final DictionaryUpdateController mPerInstanceDictionaryUpdateController =
114e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi            new DictionaryUpdateController();
115ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
1168aaae56cf6694ec75043be56f1c7812a343b24d5Yuichiro Hanada    /* A extension for a binary dictionary file. */
1178aaae56cf6694ec75043be56f1c7812a343b24d5Yuichiro Hanada    public static final String DICT_FILE_EXTENSION = ".dict";
1188aaae56cf6694ec75043be56f1c7812a343b24d5Yuichiro Hanada
119ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada    private final AtomicReference<Runnable> mUnfinishedFlushingTask =
120ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            new AtomicReference<Runnable>();
1216e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi
122ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
123ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * Abstract method for loading the unigrams and bigrams of a given dictionary in a background
124ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * thread.
125ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
126ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    protected abstract void loadDictionaryAsync();
127ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
128ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
1294d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     * Indicates that the source dictionary content has changed and a rebuild of the binary file is
1304d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     * required. If it returns false, the next reload will only read the current binary dictionary
1314d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     * from file. Note that the shared binary dictionary is locked when this is called.
1324d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     */
1334d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    protected abstract boolean hasContentChanged();
1344d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
1354d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    /**
136e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi     * Gets the dictionary update controller for the given filename.
137ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
138e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    private static DictionaryUpdateController getDictionaryUpdateController(
139ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang            String filename) {
140e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        DictionaryUpdateController recorder = sFilenameDictionaryUpdateControllerMap.get(filename);
141ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        if (recorder == null) {
142e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi            synchronized(sFilenameDictionaryUpdateControllerMap) {
143e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                recorder = new DictionaryUpdateController();
144e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                sFilenameDictionaryUpdateControllerMap.put(filename, recorder);
145ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            }
146ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        }
147ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        return recorder;
148ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada    }
149ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada
150ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada    /**
151ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada     * Gets the executor for the given filename.
152ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada     */
153ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada    private static PrioritizedSerialExecutor getExecutor(final String filename) {
154ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        PrioritizedSerialExecutor executor = sFilenameExecutorMap.get(filename);
155ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        if (executor == null) {
156ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            synchronized(sFilenameExecutorMap) {
157ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                executor = new PrioritizedSerialExecutor();
158ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                sFilenameExecutorMap.put(filename, executor);
159ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            }
160ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        }
161ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        return executor;
162ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
163ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
164c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    private static AbstractDictionaryWriter getDictionaryWriter(final Context context,
16587a72f50c23a4ef357ae623eabc2af16d02466aeKeisuke Kuroyanagi            final String dictType, final boolean isDynamicPersonalizationDictionary) {
16687a72f50c23a4ef357ae623eabc2af16d02466aeKeisuke Kuroyanagi        if (isDynamicPersonalizationDictionary) {
1672e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi            if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
1682e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                return null;
1692e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi            } else {
1702e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                return new DynamicPersonalizationDictionaryWriter(context, dictType);
1712e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi            }
172c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        } else {
173c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            return new DictionaryWriter(context, dictType);
174c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        }
175c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    }
176c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi
177ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
178ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * Creates a new expandable binary dictionary.
179ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     *
180ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * @param context The application context of the parent.
181ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * @param filename The filename for this binary dictionary. Multiple dictionaries with the same
182ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     *        filename is supported.
18305efe576f976f5fa280f8d523f2935c15cbb9bd1Jean Chalard     * @param dictType the dictionary type, as a human-readable string
184c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi     * @param isUpdatable whether to support dynamically updating the dictionary. Please note that
185c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi     *        dynamic dictionary has negative effects on memory space and computation time.
186ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
187c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    public ExpandableBinaryDictionary(final Context context, final String filename,
188c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            final String dictType, final boolean isUpdatable) {
18905efe576f976f5fa280f8d523f2935c15cbb9bd1Jean Chalard        super(dictType);
190ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        mFilename = filename;
191ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        mContext = context;
192c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        mIsUpdatable = isUpdatable;
193ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        mBinaryDictionary = null;
194e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        mFilenameDictionaryUpdateController = getDictionaryUpdateController(filename);
19587a72f50c23a4ef357ae623eabc2af16d02466aeKeisuke Kuroyanagi        // Currently, only dynamic personalization dictionary is updatable.
196c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable);
197ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
198ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
199f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang    protected static String getFilenameWithLocale(final String name, final String localeStr) {
2008aaae56cf6694ec75043be56f1c7812a343b24d5Yuichiro Hanada        return name + "." + localeStr + DICT_FILE_EXTENSION;
201f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang    }
202f6adff6227a15af105dbf39c57213a24bf16780bTom Ouyang
203ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
204ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * Closes and cleans up the binary dictionary.
205ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
206ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    @Override
207ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    public void close() {
208ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).execute(new Runnable() {
209ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
210ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
211ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                if (mBinaryDictionary!= null) {
212ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                    mBinaryDictionary.close();
213ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                    mBinaryDictionary = null;
214ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                }
2152e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                if (mDictionaryWriter != null) {
2162e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    mDictionaryWriter.close();
2172e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                }
218ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            }
219ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
220c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    }
221c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi
222c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    protected void closeBinaryDictionary() {
223ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        // Ensure that no other threads are accessing the local binary dictionary.
224ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).execute(new Runnable() {
225ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
226ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
227ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                if (mBinaryDictionary != null) {
228ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                    mBinaryDictionary.close();
229ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                    mBinaryDictionary = null;
230ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                }
231ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang            }
232ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
233ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
234ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
2355ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi    protected Map<String, String> getHeaderAttributeMap() {
2365ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi        HashMap<String, String> attributeMap = new HashMap<String, String>();
2375ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi        attributeMap.put(FormatSpec.FileHeader.SUPPORTS_DYNAMIC_UPDATE_ATTRIBUTE,
2385ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi                SUPPORTS_DYNAMIC_UPDATE);
2395ed30a7660048ef4bf78077e77554c97786eae2bKeisuke Kuroyanagi        attributeMap.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, mFilename);
2405ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi        return attributeMap;
2415ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi    }
2425ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi
2436e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi    protected void clear() {
244ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).execute(new Runnable() {
245ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
246ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
2472e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE && mDictionaryWriter == null) {
2482e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    mBinaryDictionary.close();
2492e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    final File file = new File(mContext.getFilesDir(), mFilename);
2505ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi                    BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
2515ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi                            DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
25211f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                    mBinaryDictionary = new BinaryDictionary(
25311f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                            file.getAbsolutePath(), 0 /* offset */, file.length(),
25411f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                            true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
2552e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                } else {
2562e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    mDictionaryWriter.clear();
2572e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                }
258ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            }
259ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
2606e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi    }
2616e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi
262ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
263edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi     * Adds a word unigram to the dictionary. Used for loading a dictionary.
264f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard     * @param word The word to add.
265f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard     * @param shortcutTarget A shortcut target for this word, or null if none.
266f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard     * @param frequency The frequency for this unigram.
267f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard     * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
268f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard     *   if shortcutTarget is null.
269f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard     * @param isNotAWord true if this is not a word, i.e. shortcut only.
270ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
271edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi    protected void addWord(final String word, final String shortcutTarget,
272f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard            final int frequency, final int shortcutFreq, final boolean isNotAWord) {
273f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard        mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq, isNotAWord);
274ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
275ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
276ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
277c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi     * Adds a word bigram in the dictionary. Used for loading a dictionary.
278ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
279c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    protected void addBigram(final String prevWord, final String word, final int frequency,
280c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            final long lastModifiedTime) {
281c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */,
282c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi                lastModifiedTime);
283edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi    }
284edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi
28511f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi    /**
28611f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi     * Check whether GC is needed and run GC if required.
28711f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi     */
288f36a97ab3abf7fb3766ed6ff553a2b6501d0908fKeisuke Kuroyanagi    protected void runGCIfRequired(final boolean mindsBlockByGC) {
28903cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi        if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
29011f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi        getExecutor(mFilename).execute(new Runnable() {
29111f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi            @Override
29211f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi            public void run() {
29311f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                runGCIfRequiredInternalLocked(mindsBlockByGC);
29411f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi            }
29511f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi        });
29611f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi    }
29711f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi
29811f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi    private void runGCIfRequiredInternalLocked(final boolean mindsBlockByGC) {
29911f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi        if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) return;
30011f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi        // Calls to needsToRunGC() need to be serialized.
301f36a97ab3abf7fb3766ed6ff553a2b6501d0908fKeisuke Kuroyanagi        if (mBinaryDictionary.needsToRunGC(mindsBlockByGC)) {
30203cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi            if (setIsRegeneratingIfNotRegenerating()) {
30311f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                // Run GC after currently existing time sensitive operations.
30411f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                getExecutor(mFilename).executePrioritized(new Runnable() {
30503cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi                    @Override
30603cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi                    public void run() {
30703cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi                        try {
30803cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi                            mBinaryDictionary.flushWithGC();
30903cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi                        } finally {
31003cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi                            mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
31103cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi                        }
31203cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi                    }
31303cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi                });
31403cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi            }
31503cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi        }
31603cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi    }
31703cb8f751a7f35e9159c724a2d25528b86287b57Keisuke Kuroyanagi
318edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi    /**
319c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi     * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
320edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi     */
321edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi    protected void addWordDynamically(final String word, final String shortcutTarget,
322f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard            final int frequency, final int shortcutFreq, final boolean isNotAWord) {
323c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        if (!mIsUpdatable) {
324c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename);
325c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            return;
326c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        }
327ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).execute(new Runnable() {
328ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
329ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
330e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
33111f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                    runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
332e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi                    mBinaryDictionary.addUnigramWord(word, frequency);
3332e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                } else {
3342e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    // TODO: Remove.
335f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard                    mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq,
336f3204eebb19f0f8fae9d6d81e7e2b430f29829a0Jean Chalard                            isNotAWord);
337e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi                }
338c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            }
339ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
340ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
341ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
342ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
343c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi     * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
344ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
345c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    protected void addBigramDynamically(final String word0, final String word1,
346c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            final int frequency, final boolean isValid) {
347c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        if (!mIsUpdatable) {
348c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            Log.w(TAG, "addBigramDynamically is called for non-updatable dictionary: "
349c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi                    + mFilename);
350c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            return;
351c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        }
352ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).execute(new Runnable() {
353ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
354ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
355e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
35611f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                    runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
357e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi                    mBinaryDictionary.addBigramWords(word0, word1, frequency);
3582e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                } else {
3592e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    // TODO: Remove.
3602e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    mDictionaryWriter.addBigramWords(word0, word1, frequency, isValid,
3612e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                            0 /* lastTouchedTime */);
362e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi                }
363c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            }
364ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
365c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    }
366c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi
367c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    /**
368c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi     * Dynamically remove a word bigram in the dictionary.
369c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi     */
370c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    protected void removeBigramDynamically(final String word0, final String word1) {
371c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        if (!mIsUpdatable) {
372c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            Log.w(TAG, "removeBigramDynamically is called for non-updatable dictionary: "
373c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi                    + mFilename);
374c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            return;
375c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi        }
376ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).execute(new Runnable() {
377ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
378ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
379e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
38011f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                    runGCIfRequiredInternalLocked(true /* mindsBlockByGC */);
381e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi                    mBinaryDictionary.removeBigramWords(word0, word1);
3822e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                } else {
3832e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    // TODO: Remove.
3842e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    mDictionaryWriter.removeBigramWords(word0, word1);
385e531c2241eb8d5a1462c43ce0deffaf6c769cc23Keisuke Kuroyanagi                }
386c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi            }
387ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
388ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
389ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
390ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    @Override
39140f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
3922dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard            final String prevWord, final ProximityInfo proximityInfo,
39340f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
39440f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi            final int sessionId) {
395ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        reloadDictionaryIfRequired();
396e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        if (isRegenerating()) {
397e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi            return null;
398e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        }
399ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
400ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
401ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                new AsyncResultHolder<ArrayList<SuggestedWordInfo>>();
402ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).executePrioritized(new Runnable() {
403ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
404ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
4052e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
4062e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    if (mBinaryDictionary == null) {
4072e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        holder.set(null);
4082e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        return;
4092e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    }
410edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi                    final ArrayList<SuggestedWordInfo> binarySuggestion =
41140f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi                            mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
41240f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi                                    proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
41340f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi                                    sessionId);
4142e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    holder.set(binarySuggestion);
4152e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                } else {
4162e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
4172e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                            composer.isBatchMode() ? null :
4182e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                                    mDictionaryWriter.getSuggestionsWithSessionId(composer,
4192e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                                            prevWord, proximityInfo, blockOffensiveWords,
4202e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                                            additionalFeaturesOptions, sessionId);
4212e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    // TODO: Remove checking mIsUpdatable and use native suggestion.
4222e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    if (mBinaryDictionary != null && !mIsUpdatable) {
4232e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        final ArrayList<SuggestedWordInfo> binarySuggestion =
4242e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                                mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
4252e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                                        proximityInfo, blockOffensiveWords,
4262e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                                        additionalFeaturesOptions, sessionId);
4272e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        if (inMemDictSuggestion == null) {
4282e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                            holder.set(binarySuggestion);
4292e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        } else if (binarySuggestion == null) {
4302e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                            holder.set(inMemDictSuggestion);
4312e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        } else {
4322e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                            binarySuggestion.addAll(inMemDictSuggestion);
4332e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                            holder.set(binarySuggestion);
4342e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        }
435edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi                    } else {
4362e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        holder.set(inMemDictSuggestion);
437edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi                    }
438b30d2185f24e3d531f5d46249e7c97391705e469Jean Chalard                }
439b30d2185f24e3d531f5d46249e7c97391705e469Jean Chalard            }
440ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
441ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        return holder.get(null, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
442b30d2185f24e3d531f5d46249e7c97391705e469Jean Chalard    }
443b30d2185f24e3d531f5d46249e7c97391705e469Jean Chalard
444ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    @Override
44540f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
44640f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi            final String prevWord, final ProximityInfo proximityInfo,
44740f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) {
44840f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
44940f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi                additionalFeaturesOptions, 0 /* sessionId */);
45040f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi    }
45140f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi
45240f66795a21b857276fd0601fd9bb54e58c947eeKeisuke Kuroyanagi    @Override
453bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public boolean isValidWord(final String word) {
454ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        reloadDictionaryIfRequired();
455ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        return isValidWordInner(word);
456ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
457ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
458bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    protected boolean isValidWordInner(final String word) {
459e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        if (isRegenerating()) {
460e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi            return false;
461e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        }
462ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
463ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).executePrioritized(new Runnable() {
464ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
465ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
466ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                holder.set(isValidWordLocked(word));
467ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang            }
468ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
469ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
470ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
471ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
472bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    protected boolean isValidWordLocked(final String word) {
4734d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (mBinaryDictionary == null) return false;
4744d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return mBinaryDictionary.isValidWord(word);
4754d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
4764d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
477bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    protected boolean isValidBigramLocked(final String word1, final String word2) {
4784d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        if (mBinaryDictionary == null) return false;
4794d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        return mBinaryDictionary.isValidBigram(word1, word2);
4804d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    }
4814d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang
482ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
483ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * Load the current binary dictionary from internal storage in a background thread. If no binary
484ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * dictionary exists, this method will generate one.
485ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
486ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    protected void loadDictionary() {
487e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
488ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        reloadDictionaryIfRequired();
489ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
490ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
491ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
492ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * Loads the current binary dictionary from internal storage. Assumes the dictionary file
493ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     * exists.
494ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
495edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi    private void loadBinaryDictionary() {
496ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        if (DEBUG) {
4971ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang            Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
498e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
499e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    + mFilenameDictionaryUpdateController.mLastUpdateTime);
500ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        }
501ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
502ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        final File file = new File(mContext.getFilesDir(), mFilename);
503ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        final String filename = file.getAbsolutePath();
504ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        final long length = file.length();
505ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
506ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        // Build the new binary dictionary
50711f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi        final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0 /* offset */,
50811f7cae094720c3ab47e6c18772b1fc44e9e5372Keisuke Kuroyanagi                length, true /* useFullEditDistance */, null, mDictType, mIsUpdatable);
509ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
5102e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi        // Ensure all threads accessing the current dictionary have finished before
5112e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi        // swapping in the new one.
5122e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi        // TODO: Ensure multi-thread assignment of mBinaryDictionary.
513ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
514ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).executePrioritized(new Runnable() {
515ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
516ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
5170b1c08bf5aad0c6775acb1acb7048191854851abKeisuke Kuroynagi                mBinaryDictionary = newBinaryDictionary;
518ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                if (oldBinaryDictionary != null) {
519ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                    oldBinaryDictionary.close();
520ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                }
5210b1c08bf5aad0c6775acb1acb7048191854851abKeisuke Kuroynagi            }
522ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
523ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
524ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
525ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
526edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi     * Abstract method for checking if it is required to reload the dictionary before writing
527edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi     * a binary dictionary.
528edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi     */
529edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi    abstract protected boolean needsToReloadBeforeWriting();
530edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi
531edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi    /**
532c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi     * Writes a new binary dictionary based on the contents of the fusion dictionary.
533ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
534c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi    private void writeBinaryDictionary() {
535ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        if (DEBUG) {
5361ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang            Log.d(TAG, "Generating binary dictionary: " + mFilename + " request="
537e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
538e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    + mFilenameDictionaryUpdateController.mLastUpdateTime);
539ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        }
540edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi        if (needsToReloadBeforeWriting()) {
541edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi            mDictionaryWriter.clear();
542edd1992ed329a84f0e9ef7056fda99f78eeb92b4Keisuke Kuroynagi            loadDictionaryAsync();
5435ed30a7660048ef4bf78077e77554c97786eae2bKeisuke Kuroyanagi            mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
5442e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi        } else {
5452e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi            if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
5462e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                if (mBinaryDictionary == null || !mBinaryDictionary.isValidDictionary()) {
5472e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    final File file = new File(mContext.getFilesDir(), mFilename);
5485ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi                    BinaryDictionary.createEmptyDictFile(file.getAbsolutePath(),
5495ef6209656c51df0f0542d2a75c2df93c8d0f027Keisuke Kuroyanagi                            DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
5502e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                } else {
551c18510049a3422c88ed3ab3bbc64944c94a611fdKeisuke Kuroyanagi                    if (mBinaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
5522e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        mBinaryDictionary.flushWithGC();
5532e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    } else {
5542e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        mBinaryDictionary.flush();
5552e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    }
5562e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                }
5572e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi            } else {
5585ed30a7660048ef4bf78077e77554c97786eae2bKeisuke Kuroyanagi                mDictionaryWriter.write(mFilename, getHeaderAttributeMap());
5592e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi            }
560ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        }
561ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
562ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
563ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
5644d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     * Marks that the dictionary is out of date and requires a reload.
5654d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     *
5664d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     * @param requiresRebuild Indicates that the source dictionary content has changed and a rebuild
5674d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     *        of the binary file is required. If not true, the next reload process will only read
5684d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang     *        the current binary dictionary from file.
569ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
5704d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    protected void setRequiresReload(final boolean requiresRebuild) {
5714d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang        final long time = SystemClock.uptimeMillis();
572e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = time;
573e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        mFilenameDictionaryUpdateController.mLastUpdateRequestTime = time;
574ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        if (DEBUG) {
5751ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang            Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update="
576e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    + mFilenameDictionaryUpdateController.mLastUpdateTime);
577ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        }
578ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
579ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
580ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
5811ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang     * Reloads the dictionary if required.
582ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
583ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada    public final void reloadDictionaryIfRequired() {
5841ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang        if (!isReloadRequired()) return;
585e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        if (setIsRegeneratingIfNotRegenerating()) {
586e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi            reloadDictionary();
587e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        }
5881ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang    }
5891ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang
5901ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang    /**
5911ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang     * Returns whether a dictionary reload is required.
5921ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang     */
5931ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang    private boolean isReloadRequired() {
594e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        return mBinaryDictionary == null || mPerInstanceDictionaryUpdateController.isOutOfDate();
595e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    }
596e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi
597e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    private boolean isRegenerating() {
598e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        return mFilenameDictionaryUpdateController.mIsRegenerating.get();
599e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    }
600e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi
601e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    // Returns whether the dictionary can be regenerated.
602e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    private boolean setIsRegeneratingIfNotRegenerating() {
603e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        return mFilenameDictionaryUpdateController.mIsRegenerating.compareAndSet(
604e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                false /* expect */ , true /* update */);
6051ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang    }
606ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
6071ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang    /**
6081ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang     * Reloads the dictionary. Access is controlled on a per dictionary file basis and supports
6091ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang     * concurrent calls from multiple instances that share the same dictionary file.
6101ed017ef0e271ed3f3c212def6cc6ba95b14e780Tom Ouyang     */
611ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada    private final void reloadDictionary() {
612ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        // Ensure that only one thread attempts to read or write to the shared binary dictionary
613ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        // file at the same time.
614ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).execute(new Runnable() {
615ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
616ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
617e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                try {
618e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    final long time = SystemClock.uptimeMillis();
619e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    final boolean dictionaryFileExists = dictionaryFileExists();
620e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    if (mFilenameDictionaryUpdateController.isOutOfDate()
621e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            || !dictionaryFileExists) {
622e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        // If the shared dictionary file does not exist or is out of date, the
623e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        // first instance that acquires the lock will generate a new one.
624e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        if (hasContentChanged() || !dictionaryFileExists) {
625e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            // If the source content has changed or the dictionary does not exist,
626e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            // rebuild the binary dictionary. Empty dictionaries are supported (in
627e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            // the case where loadDictionaryAsync() adds nothing) in order to
628e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            // provide a uniform framework.
629e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            mFilenameDictionaryUpdateController.mLastUpdateTime = time;
630e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            writeBinaryDictionary();
631e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            loadBinaryDictionary();
632e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        } else {
633e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            // If not, the reload request was unnecessary so revert
634e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            // LastUpdateRequestTime to LastUpdateTime.
635e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            mFilenameDictionaryUpdateController.mLastUpdateRequestTime =
636e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                                    mFilenameDictionaryUpdateController.mLastUpdateTime;
637e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        }
638e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    } else if (mBinaryDictionary == null ||
639e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                            mPerInstanceDictionaryUpdateController.mLastUpdateTime
640e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                                    < mFilenameDictionaryUpdateController.mLastUpdateTime) {
641e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        // Otherwise, if the local dictionary is older than the shared dictionary,
642e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        // load the shared dictionary.
643e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        loadBinaryDictionary();
644e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    }
645e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
646e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        // Binary dictionary is not valid. Regenerate the dictionary file.
647e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                        mFilenameDictionaryUpdateController.mLastUpdateTime = time;
648c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi                        writeBinaryDictionary();
649c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi                        loadBinaryDictionary();
650c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi                    }
651e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
652e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                } finally {
653e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi                    mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
6544d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang                }
655a62b5b22eff2c1842fe1e0a4ea949e1e004de40bKeisuke Kuroynagi            }
656ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
657ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
658ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
6594d289d39aeae21064f63d958974816ceee3e9fdeTom Ouyang    // TODO: cache the file's existence so that we avoid doing a disk access each time.
660ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    private boolean dictionaryFileExists() {
661ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        final File file = new File(mContext.getFilesDir(), mFilename);
662ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        return file.exists();
663ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
664ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
665ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    /**
6666e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi     * Load the dictionary to memory.
6676e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi     */
6686e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi    protected void asyncLoadDictionaryToMemory() {
669ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).executePrioritized(new Runnable() {
670ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
671ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
6722e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                if (!ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
6732e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    loadDictionaryAsync();
6742e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                }
6756e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi            }
676ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
6776e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi    }
6786e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi
6796e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi    /**
6806e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi     * Generate binary dictionary using DictionaryWriter.
6816e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi     */
6822e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi    protected void asyncFlashAllBinaryDictionary() {
683ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        final Runnable newTask = new Runnable() {
684ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
685ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
686ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                writeBinaryDictionary();
6876e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi            }
688ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        };
689ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        final Runnable oldTask = mUnfinishedFlushingTask.getAndSet(newTask);
690ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).replaceAndExecute(oldTask, newTask);
6916e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi    }
6926e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi
6936e04d6593239e841f5dac0d3f32d613967c11e22Keisuke Kuroyanagi    /**
694e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi     * For tracking whether the dictionary is out of date and the dictionary is regenerating.
695ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada     * Can be shared across multiple dictionary instances that access the same filename.
696ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang     */
697e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi    private static class DictionaryUpdateController {
698e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        public volatile long mLastUpdateTime = 0;
699e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        public volatile long mLastUpdateRequestTime = 0;
700e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        public volatile AtomicBoolean mIsRegenerating = new AtomicBoolean();
701ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang
702e74d4a184bbd06ddb607f81147ed827b9dd1ba17Keisuke Kuroyanagi        public boolean isOutOfDate() {
703ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang            return (mLastUpdateRequestTime > mLastUpdateTime);
704ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang        }
705ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang    }
706c8db6f21e936b819a0b818f44eae0d2bc44433c9Keisuke Kuroyanagi
707e5a35711b854aedeeea2f45105b941b9deee49bcSatoshi Kataoka    // TODO: Implement native binary methods once the dynamic dictionary implementation is done.
708e5a35711b854aedeeea2f45105b941b9deee49bcSatoshi Kataoka    @UsedForTesting
709e5a35711b854aedeeea2f45105b941b9deee49bcSatoshi Kataoka    public boolean isInDictionaryForTests(final String word) {
710ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
711ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        getExecutor(mFilename).executePrioritized(new Runnable() {
712ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            @Override
713ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada            public void run() {
714ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                if (mDictType == Dictionary.TYPE_USER_HISTORY) {
7152e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    if (ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
7162e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        holder.set(mBinaryDictionary.isValidWord(word));
7172e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    } else {
7182e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                        holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
719a328f538c34ad2dafdfa53642085cb1072224d80Yuichiro Hanada                                .isInBigramListForTests(word));
7202e58670da9687fd1fd28c322e03343957d11568cKeisuke Kuroyanagi                    }
721ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada                }
722e5a35711b854aedeeea2f45105b941b9deee49bcSatoshi Kataoka            }
723ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        });
724ef073f402407b19f5be90ddf68beb874945e82beYuichiro Hanada        return holder.get(false, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS);
725e5a35711b854aedeeea2f45105b941b9deee49bcSatoshi Kataoka    }
726a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada
727a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada    @UsedForTesting
728a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada    public void shutdownExecutorForTests() {
729a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada        getExecutor(mFilename).shutdown();
730a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada    }
731a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada
732a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada    @UsedForTesting
733a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada    public boolean isTerminatedForTests() {
734a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada        return getExecutor(mFilename).isTerminated();
735a099a3e341d8de0512c8bb8f4dbe352456f2a4a4Yuichiro Hanada    }
736ecd2ac93bc321fdd932930c43851a92859d4775dTom Ouyang}
737