1d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard/*
2d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * Copyright (C) 2013 The Android Open Source Project
3d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard *
4d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * Licensed under the Apache License, Version 2.0 (the "License");
5d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * you may not use this file except in compliance with the License.
6d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * You may obtain a copy of the License at
7d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard *
8d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard *      http://www.apache.org/licenses/LICENSE-2.0
9d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard *
10d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * Unless required by applicable law or agreed to in writing, software
11d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * distributed under the License is distributed on an "AS IS" BASIS,
12d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * See the License for the specific language governing permissions and
14d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * limitations under the License.
15d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard */
16d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
17e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasapackage com.android.inputmethod.latin.utils;
18d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
193623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalardimport android.content.ContentValues;
20d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalardimport android.content.Context;
21af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalardimport android.content.res.AssetManager;
22d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalardimport android.content.res.Resources;
2384a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalardimport android.text.TextUtils;
24d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalardimport android.util.Log;
253bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheikimport android.view.inputmethod.InputMethodSubtype;
26d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
278c889784e2d20bb3ebc1ad869176a791a755ccc6Adrian Velicuimport com.android.inputmethod.annotations.UsedForTesting;
283a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheikimport com.android.inputmethod.dictionarypack.UpdateHandler;
29e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.AssetFileAddress;
30e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.BinaryDictionaryGetter;
31e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.R;
323bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheikimport com.android.inputmethod.latin.RichInputMethodManager;
33a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheikimport com.android.inputmethod.latin.common.FileUtils;
345b91b551e5ffaf2c2e691dfbd434f21c82293986Jean Chalardimport com.android.inputmethod.latin.common.LocaleUtils;
350f7d881dc72132dfd75c8b4fe61a69fc5cdcd460Mohammadinamul Sheikimport com.android.inputmethod.latin.define.DecoderSpecificConstants;
36b986f78ba826fa360304a69565f1880bdd7ce0c5Keisuke Kuroyanagiimport com.android.inputmethod.latin.makedict.DictionaryHeader;
37de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagiimport com.android.inputmethod.latin.makedict.UnsupportedFormatException;
3884a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalardimport com.android.inputmethod.latin.settings.SpacingAndPunctuations;
39d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
40d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalardimport java.io.File;
413a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheikimport java.io.FilenameFilter;
42de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagiimport java.io.IOException;
43af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalardimport java.util.ArrayList;
440dc422e0c7bbb5f2f5ea52dc87e3caf36b545e6bJean Chalardimport java.util.Iterator;
453bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheikimport java.util.List;
46d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalardimport java.util.Locale;
4772c2feb57369527b5f0d2b89505f94503978b928Tadashi G. Takaokaimport java.util.concurrent.TimeUnit;
48d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
49ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaokaimport javax.annotation.Nonnull;
50ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaokaimport javax.annotation.Nullable;
51ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka
52d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard/**
53d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard * This class encapsulates the logic for the Latin-IME side of dictionary information management.
54d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard */
55d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalardpublic class DictionaryInfoUtils {
56d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
573aad142435510feb575a6a9af0581fc84df6cabdDan Zivkovic    public static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
58d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    private static final String DEFAULT_MAIN_DICT = "main";
59d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    private static final String MAIN_DICT_PREFIX = "main_";
6095711bfcee07d848883316cf07439408f5b332a1Mohammadinamul Sheik    private static final String DECODER_DICT_SUFFIX = DecoderSpecificConstants.DECODER_DICT_SUFFIX;
61d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    // 6 digits - unicode is limited to 21 bits
62d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
63d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
643a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik    private static final String TEMP_DICT_FILE_SUB = UpdateHandler.TEMP_DICT_FILE_SUB;
653a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik
66af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard    public static class DictionaryInfo {
673623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard        private static final String LOCALE_COLUMN = "locale";
683623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard        private static final String WORDLISTID_COLUMN = "id";
693623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard        private static final String LOCAL_FILENAME_COLUMN = "filename";
70c6799ffeab17d3e0dc54a1718dad9890e5493ae0Jean Chalard        private static final String DESCRIPTION_COLUMN = "description";
713623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard        private static final String DATE_COLUMN = "date";
723623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard        private static final String FILESIZE_COLUMN = "filesize";
733623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard        private static final String VERSION_COLUMN = "version";
743a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik
753a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        @Nonnull public final String mId;
763a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        @Nonnull public final Locale mLocale;
773a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        @Nullable public final String mDescription;
783a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        @Nullable public final String mFilename;
793a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        public final long mFilesize;
803a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        public final long mModifiedTimeMillis;
81af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        public final int mVersion;
82ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka
833a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        public DictionaryInfo(@Nonnull String id, @Nonnull Locale locale,
843a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                @Nullable String description, @Nullable String filename,
853a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                long filesize, long modifiedTimeMillis, int version) {
862521edec09373b2810093462c89221a2aca9e369Jean Chalard            mId = id;
87af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            mLocale = locale;
88c6799ffeab17d3e0dc54a1718dad9890e5493ae0Jean Chalard            mDescription = description;
893a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            mFilename = filename;
903a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            mFilesize = filesize;
913a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            mModifiedTimeMillis = modifiedTimeMillis;
92af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            mVersion = version;
933623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard        }
94ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka
953623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard        public ContentValues toContentValues() {
963623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard            final ContentValues values = new ContentValues();
973623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard            values.put(WORDLISTID_COLUMN, mId);
983623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard            values.put(LOCALE_COLUMN, mLocale.toString());
99c6799ffeab17d3e0dc54a1718dad9890e5493ae0Jean Chalard            values.put(DESCRIPTION_COLUMN, mDescription);
1003a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            values.put(LOCAL_FILENAME_COLUMN, mFilename != null ? mFilename : "");
1013a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(mModifiedTimeMillis));
1023a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            values.put(FILESIZE_COLUMN, mFilesize);
1033623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard            values.put(VERSION_COLUMN, mVersion);
1043623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard            return values;
105af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        }
1063aad142435510feb575a6a9af0581fc84df6cabdDan Zivkovic
1073aad142435510feb575a6a9af0581fc84df6cabdDan Zivkovic        @Override
1083aad142435510feb575a6a9af0581fc84df6cabdDan Zivkovic        public String toString() {
1093aad142435510feb575a6a9af0581fc84df6cabdDan Zivkovic            return "DictionaryInfo : Id = '" + mId
1103aad142435510feb575a6a9af0581fc84df6cabdDan Zivkovic                    + "' : Locale=" + mLocale
1113aad142435510feb575a6a9af0581fc84df6cabdDan Zivkovic                    + " : Version=" + mVersion;
1123aad142435510feb575a6a9af0581fc84df6cabdDan Zivkovic        }
113af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard    }
114af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard
115d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    private DictionaryInfoUtils() {
116d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // Private constructor to forbid instantation of this helper class.
117d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
118d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
119d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
120d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Returns whether we may want to use this character as part of a file name.
121d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     *
122d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * This basically only accepts ascii letters and numbers, and rejects everything else.
123d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
124d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    private static boolean isFileNameCharacter(int codePoint) {
125d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit
126d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase
127d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase
128d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return codePoint == '_'; // Underscore
129d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
130d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
131d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
132d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Escapes a string for any characters that may be suspicious for a file or directory name.
133d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     *
134d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Concretely this does a sort of URL-encoding except it will encode everything that's not
135d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which
136d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * we cannot allow here)
137d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
138d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    // TODO: create a unit test for this method
139d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    public static String replaceFileNameDangerousCharacters(final String name) {
140d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // This assumes '%' is fully available as a non-separator, normal
141d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // character in a file name. This is probably true for all file systems.
142d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final StringBuilder sb = new StringBuilder();
143d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final int nameLength = name.length();
144d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) {
145d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            final int codePoint = name.codePointAt(i);
146d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            if (DictionaryInfoUtils.isFileNameCharacter(codePoint)) {
147d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                sb.appendCodePoint(codePoint);
148d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            } else {
149d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                sb.append(String.format((Locale)null, "%%%1$0" + MAX_HEX_DIGITS_FOR_CODEPOINT + "x",
150d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                        codePoint));
151d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            }
152d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        }
153d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return sb.toString();
154d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
155d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
156d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
157d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Helper method to get the top level cache directory.
158d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
159d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    private static String getWordListCacheDirectory(final Context context) {
160d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return context.getFilesDir() + File.separator + "dicts";
161d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
162d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
163d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
164a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik     * Helper method to get the top level cache directory.
165a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik     */
166a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    public static String getWordListStagingDirectory(final Context context) {
167a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        return context.getFilesDir() + File.separator + "staging";
168a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    }
169a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik
170a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    /**
171a995bf4031dd19ff9c17dd4bf7969c713dc16dcfJean Chalard     * Helper method to get the top level temp directory.
172a995bf4031dd19ff9c17dd4bf7969c713dc16dcfJean Chalard     */
173a995bf4031dd19ff9c17dd4bf7969c713dc16dcfJean Chalard    public static String getWordListTempDirectory(final Context context) {
174a995bf4031dd19ff9c17dd4bf7969c713dc16dcfJean Chalard        return context.getFilesDir() + File.separator + "tmp";
175a995bf4031dd19ff9c17dd4bf7969c713dc16dcfJean Chalard    }
176a995bf4031dd19ff9c17dd4bf7969c713dc16dcfJean Chalard
177a995bf4031dd19ff9c17dd4bf7969c713dc16dcfJean Chalard    /**
178377ba98b753c63a5d25d2e139533191aa18e4c0dMohammadinamul Sheik     * Reverse escaping done by {@link #replaceFileNameDangerousCharacters(String)}.
179d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
180ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka    @Nonnull
181ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka    public static String getWordListIdFromFileName(@Nonnull final String fname) {
182d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final StringBuilder sb = new StringBuilder();
183d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final int fnameLength = fname.length();
184d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
185d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            final int codePoint = fname.codePointAt(i);
186d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            if ('%' != codePoint) {
187d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                sb.appendCodePoint(codePoint);
188d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            } else {
189d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                // + 1 to pass the % sign
190d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                final int encodedCodePoint = Integer.parseInt(
191d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                        fname.substring(i + 1, i + 1 + MAX_HEX_DIGITS_FOR_CODEPOINT), 16);
192d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                i += MAX_HEX_DIGITS_FOR_CODEPOINT;
193d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                sb.appendCodePoint(encodedCodePoint);
194d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            }
195d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        }
196d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return sb.toString();
197d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
198d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
199d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
200d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Helper method to the list of cache directories, one for each distinct locale.
201d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
202d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    public static File[] getCachedDirectoryList(final Context context) {
203d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return new File(DictionaryInfoUtils.getWordListCacheDirectory(context)).listFiles();
204d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
205d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
206a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    public static File[] getStagingDirectoryList(final Context context) {
207a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        return new File(DictionaryInfoUtils.getWordListStagingDirectory(context)).listFiles();
208a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    }
209a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik
2103a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik    @Nullable
2113a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik    public static File[] getUnusedDictionaryList(final Context context) {
2123a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        return context.getFilesDir().listFiles(new FilenameFilter() {
2133a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            @Override
2143a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            public boolean accept(File dir, String filename) {
2153a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                return !TextUtils.isEmpty(filename) && filename.endsWith(".dict")
2163a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                        && filename.contains(TEMP_DICT_FILE_SUB);
2173a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            }
2183a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        });
2193a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik    }
2203a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik
221d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
222d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Returns the category for a given file name.
223d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     *
224d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * This parses the file name, extracts the category, and returns it. See
225d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}.
226d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * @return The category as a string or null if it can't be found in the file name.
227d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
228ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka    @Nullable
229ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka    public static String getCategoryFromFileName(@Nonnull final String fileName) {
230d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final String id = getWordListIdFromFileName(fileName);
231d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
232d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // An id is supposed to be in format category:locale, so splitting on the separator
233d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // should yield a 2-elements array
234ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka        if (2 != idArray.length) {
235ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka            return null;
236ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka        }
237d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return idArray[0];
238d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
239d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
240d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
241d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Find out the cache directory associated with a specific locale.
242d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
243a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    public static String getCacheDirectoryForLocale(final String locale, final Context context) {
244d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale);
245d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator
246d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                + relativeDirectoryName;
247d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final File directory = new File(absoluteDirectoryName);
248d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        if (!directory.exists()) {
249d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            if (!directory.mkdirs()) {
250d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                Log.e(TAG, "Could not create the directory for locale" + locale);
251d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            }
252d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        }
253d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return absoluteDirectoryName;
254d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
255d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
256d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
257d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Generates a file name for the id and locale passed as an argument.
258d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     *
259d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * In the current implementation the file name returned will always be unique for
260d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * any id/locale pair, but please do not expect that the id can be the same for
261d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * different dictionaries with different locales. An id should be unique for any
262d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * dictionary.
263d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * The file name is pretty much an URL-encoded version of the id inside a directory
264d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * named like the locale, except it will also escape characters that look dangerous
265d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * to some file systems.
266d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * @param id the id of the dictionary for which to get a file name
267d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * @param locale the locale for which to get the file name as a string
268d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * @param context the context to use for getting the directory
269d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * @return the name of the file to be created
270d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
271d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    public static String getCacheFileName(String id, String locale, Context context) {
272d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final String fileName = replaceFileNameDangerousCharacters(id);
273d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
274d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
275d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
276a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    public static String getStagingFileName(String id, String locale, Context context) {
277a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        final String stagingDirectory = getWordListStagingDirectory(context);
278a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        // create the directory if it does not exist.
279a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        final File directory = new File(stagingDirectory);
280a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        if (!directory.exists()) {
281a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik            if (!directory.mkdirs()) {
282a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                Log.e(TAG, "Could not create the staging directory.");
283a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik            }
284a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        }
285a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        // e.g. id="main:en_in", locale ="en_IN"
286a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        final String fileName = replaceFileNameDangerousCharacters(
287a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                locale + TEMP_DICT_FILE_SUB + id);
288a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        return stagingDirectory + File.separator + fileName;
289a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    }
290a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik
291a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    public static void moveStagingFilesIfExists(Context context) {
292a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        final File[] stagingFiles = DictionaryInfoUtils.getStagingDirectoryList(context);
293a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        if (stagingFiles != null && stagingFiles.length > 0) {
294a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik            for (final File stagingFile : stagingFiles) {
295a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                final String fileName = stagingFile.getName();
296a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                final int index = fileName.indexOf(TEMP_DICT_FILE_SUB);
297a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                if (index == -1) {
298a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                    // This should never happen.
299a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                    Log.e(TAG, "Staging file does not have ___ substring.");
300a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                    continue;
301a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                }
302a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                final String[] localeAndFileId = fileName.split(TEMP_DICT_FILE_SUB);
303a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                if (localeAndFileId.length != 2) {
304a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                    Log.e(TAG, String.format("malformed staging file %s. Deleting.",
305a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                            stagingFile.getAbsoluteFile()));
306a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                    stagingFile.delete();
307a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                    continue;
308a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                }
309a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik
310a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                final String locale = localeAndFileId[0];
311a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                // already escaped while moving to staging.
312a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                final String fileId = localeAndFileId[1];
313a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                final String cacheDirectoryForLocale = getCacheDirectoryForLocale(locale, context);
314a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                final String cacheFilename = cacheDirectoryForLocale + File.separator + fileId;
315a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                final File cacheFile = new File(cacheFilename);
316a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik                // move the staging file to cache file.
317d711426a87ed131c527beb8f7a9160daa14d1f94Jatin Matani                if (!FileUtils.renameTo(stagingFile, cacheFile)) {
318d711426a87ed131c527beb8f7a9160daa14d1f94Jatin Matani                    Log.e(TAG, String.format("Failed to rename from %s to %s.",
319d711426a87ed131c527beb8f7a9160daa14d1f94Jatin Matani                            stagingFile.getAbsoluteFile(), cacheFile.getAbsoluteFile()));
320d711426a87ed131c527beb8f7a9160daa14d1f94Jatin Matani                }
321a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik            }
322a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik        }
323a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik    }
324a0d9c82921022347e44d416bb57810331e35e446Mohammadinamul Sheik
325d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    public static boolean isMainWordListId(final String id) {
326d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
327d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // An id is supposed to be in format category:locale, so splitting on the separator
328d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // should yield a 2-elements array
329ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka        if (2 != idArray.length) {
330ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka            return false;
331ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka        }
332d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
333d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
334d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
335d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
336a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka     * Find out whether a dictionary is available for this locale.
337a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka     * @param context the context on which to check resources.
338a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka     * @param locale the locale to check for.
339a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka     * @return whether a (non-placeholder) dictionary is available or not.
340a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka     */
341a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka    public static boolean isDictionaryAvailable(final Context context, final Locale locale) {
342a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka        final Resources res = context.getResources();
343a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka        return 0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
344a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka    }
345a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka
346a414b81a6c69e89ce36c3756560062af84af4027Tadashi G. Takaoka    /**
347d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Helper method to return a dictionary res id for a locale, or 0 if none.
348f13487dfbf2b7547b48a5e9123235ee8a1d660c8Jean Chalard     * @param res resources for the app
349d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * @param locale dictionary locale
350d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * @return main dictionary resource id
351d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
352d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    public static int getMainDictionaryResourceIdIfAvailableForLocale(final Resources res,
353d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            final Locale locale) {
354d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        int resId;
355d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // Try to find main_language_country dictionary.
356d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        if (!locale.getCountry().isEmpty()) {
35795711bfcee07d848883316cf07439408f5b332a1Mohammadinamul Sheik            final String dictLanguageCountry = MAIN_DICT_PREFIX
35895711bfcee07d848883316cf07439408f5b332a1Mohammadinamul Sheik                    + locale.toString().toLowerCase(Locale.ROOT) + DECODER_DICT_SUFFIX;
359d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            if ((resId = res.getIdentifier(
360d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                    dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
361d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard                return resId;
362d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            }
363d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        }
364d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
365d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // Try to find main_language dictionary.
36695711bfcee07d848883316cf07439408f5b332a1Mohammadinamul Sheik        final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage() + DECODER_DICT_SUFFIX;
367d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
368d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard            return resId;
369d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        }
370d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
371d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // Not found, return 0
372d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return 0;
373d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
374d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
375d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
376d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Returns a main dictionary resource id
377f13487dfbf2b7547b48a5e9123235ee8a1d660c8Jean Chalard     * @param res resources for the app
378d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * @param locale dictionary locale
379d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * @return main dictionary resource id
380d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
381d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    public static int getMainDictionaryResourceId(final Resources res, final Locale locale) {
382d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
383ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka        if (0 != resourceId) {
384ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka            return resourceId;
385ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka        }
3861d5ec6136cf2d2c84453120407957ddb64c7b2b7Mohammadinamul Sheik        return res.getIdentifier(DEFAULT_MAIN_DICT + DecoderSpecificConstants.DECODER_DICT_SUFFIX,
3871d5ec6136cf2d2c84453120407957ddb64c7b2b7Mohammadinamul Sheik                "raw", RESOURCE_PACKAGE_NAME);
388d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
389d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard
390d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    /**
391d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Returns the id associated with the main word list for a specified locale.
392d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     *
393d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * Word lists stored in Android Keyboard's resources are referred to as the "main"
394d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * word lists. Since they can be updated like any other list, we need to assign a
395d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * unique ID to them. This ID is just the name of the language (locale-wise) they
396d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     * are for, and this method returns this ID.
397d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard     */
3988f526c9a55e0b30f81dcca07dc4f5f4fd341bdb1Mohammadinamul Sheik    public static String getMainDictId(@Nonnull final Locale locale) {
399d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // This works because we don't include by default different dictionaries for
400d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // different countries. This actually needs to return the id that we would
401d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        // like to use for word lists included in resources, and the following is okay.
402d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard        return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY +
4038f526c9a55e0b30f81dcca07dc4f5f4fd341bdb1Mohammadinamul Sheik                BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase();
404de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi    }
405de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi
4066e29d261cb480ef0f0d90867d4582fabce0b9113Mohammadinamul Sheik    public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
407de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi            final long offset, final long length) {
408de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi        try {
409de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi            final DictionaryHeader header =
410de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi                    BinaryDictionaryUtils.getHeaderWithOffsetAndLength(file, offset, length);
411de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi            return header;
412de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi        } catch (UnsupportedFormatException e) {
413de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi            return null;
414de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi        } catch (IOException e) {
415de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi            return null;
416de36b47d29b7d6bdfb448a97bef2dcc3f5649205Keisuke Kuroyanagi        }
417d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard    }
418af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard
4198214a8c2cf29ef28ccf515b9df1390299fd4cbddKeisuke Kuroyanagi    /**
4208214a8c2cf29ef28ccf515b9df1390299fd4cbddKeisuke Kuroyanagi     * Returns information of the dictionary.
4218214a8c2cf29ef28ccf515b9df1390299fd4cbddKeisuke Kuroyanagi     *
4228214a8c2cf29ef28ccf515b9df1390299fd4cbddKeisuke Kuroyanagi     * @param fileAddress the asset dictionary file address.
4236e29d261cb480ef0f0d90867d4582fabce0b9113Mohammadinamul Sheik     * @param locale Locale for this file.
4248214a8c2cf29ef28ccf515b9df1390299fd4cbddKeisuke Kuroyanagi     * @return information of the specified dictionary.
4258214a8c2cf29ef28ccf515b9df1390299fd4cbddKeisuke Kuroyanagi     */
426af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard    private static DictionaryInfo createDictionaryInfoFromFileAddress(
4273a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            @Nonnull final AssetFileAddress fileAddress, final Locale locale) {
4283a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        final String id = getMainDictId(locale);
4293a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        final int version = DictionaryHeaderUtils.getContentVersion(fileAddress);
4303a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        final String description = SubtypeLocaleUtils
4313a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                .getSubtypeLocaleDisplayName(locale.toString());
4323a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        // Do not store the filename on db as it will try to move the filename from db to the
4333a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        // cached directory. If the filename is already in cached directory, this is not
4343a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        // necessary.
4353a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        final String filenameToStoreOnDb = null;
4363a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        return new DictionaryInfo(id, locale, description, filenameToStoreOnDb,
4373a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                fileAddress.mLength, new File(fileAddress.mFilename).lastModified(), version);
4383a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik    }
4393a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik
4403a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik    /**
4413a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik     * Returns the information of the dictionary for the given {@link AssetFileAddress}.
4423a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik     * If the file is corrupted or a pre-fava file, then the file gets deleted and the null
4433a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik     * value is returned.
4443a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik     */
4453a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik    @Nullable
4463a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik    private static DictionaryInfo createDictionaryInfoForUnCachedFile(
4473a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            @Nonnull final AssetFileAddress fileAddress, final Locale locale) {
4486e29d261cb480ef0f0d90867d4582fabce0b9113Mohammadinamul Sheik        final String id = getMainDictId(locale);
4496e29d261cb480ef0f0d90867d4582fabce0b9113Mohammadinamul Sheik        final int version = DictionaryHeaderUtils.getContentVersion(fileAddress);
4503a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik
4513a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        if (version == -1) {
4523a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            // Purge the pre-fava/corrupted unused dictionaires.
4533a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            fileAddress.deleteUnderlyingFile();
4543a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            return null;
4553a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        }
4563a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik
4576e29d261cb480ef0f0d90867d4582fabce0b9113Mohammadinamul Sheik        final String description = SubtypeLocaleUtils
4586e29d261cb480ef0f0d90867d4582fabce0b9113Mohammadinamul Sheik                .getSubtypeLocaleDisplayName(locale.toString());
4593a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik
4603a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        final File unCachedFile = new File(fileAddress.mFilename);
4613a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        // Store just the filename and not the full path.
4623a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        final String filenameToStoreOnDb = unCachedFile.getName();
4633a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        return new DictionaryInfo(id, locale, description, filenameToStoreOnDb, fileAddress.mLength,
4643a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                unCachedFile.lastModified(), version);
465af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard    }
466af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard
4673bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik    /**
4683bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik     * Returns dictionary information for the given locale.
4693bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik     */
4703bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik    private static DictionaryInfo createDictionaryInfoFromLocale(Locale locale) {
4713bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik        final String id = getMainDictId(locale);
4723bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik        final int version = -1;
4733bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik        final String description = SubtypeLocaleUtils
4743bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik                .getSubtypeLocaleDisplayName(locale.toString());
4753a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        return new DictionaryInfo(id, locale, description, null, 0L, 0L, version);
4763bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik    }
4773bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik
478af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard    private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList,
479af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            final DictionaryInfo newElement) {
4800dc422e0c7bbb5f2f5ea52dc87e3caf36b545e6bJean Chalard        final Iterator<DictionaryInfo> iter = dictList.iterator();
4810dc422e0c7bbb5f2f5ea52dc87e3caf36b545e6bJean Chalard        while (iter.hasNext()) {
4820dc422e0c7bbb5f2f5ea52dc87e3caf36b545e6bJean Chalard            final DictionaryInfo thisDictInfo = iter.next();
4830dc422e0c7bbb5f2f5ea52dc87e3caf36b545e6bJean Chalard            if (thisDictInfo.mLocale.equals(newElement.mLocale)) {
4840dc422e0c7bbb5f2f5ea52dc87e3caf36b545e6bJean Chalard                if (newElement.mVersion <= thisDictInfo.mVersion) {
485af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard                    return;
486af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard                }
4870dc422e0c7bbb5f2f5ea52dc87e3caf36b545e6bJean Chalard                iter.remove();
488af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            }
489af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        }
490af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        dictList.add(newElement);
491af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard    }
492af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard
493af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard    public static ArrayList<DictionaryInfo> getCurrentDictionaryFileNameAndVersionInfo(
494af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            final Context context) {
495a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka        final ArrayList<DictionaryInfo> dictList = new ArrayList<>();
496af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard
4973a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        // Retrieve downloaded dictionaries from cached directories
498af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        final File[] directoryList = getCachedDirectoryList(context);
4993623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard        if (null != directoryList) {
5003623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard            for (final File directory : directoryList) {
5013623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                final String localeString = getWordListIdFromFileName(directory.getName());
502ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                final File[] dicts = BinaryDictionaryGetter.getCachedWordLists(
503ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                        localeString, context);
5043623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                for (final File dict : dicts) {
5053623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                    final String wordListId = getWordListIdFromFileName(dict.getName());
506ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                    if (!DictionaryInfoUtils.isMainWordListId(wordListId)) {
507ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                        continue;
508ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                    }
5093623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                    final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
5103623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                    final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict);
5113623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                    final DictionaryInfo dictionaryInfo =
5126e29d261cb480ef0f0d90867d4582fabce0b9113Mohammadinamul Sheik                            createDictionaryInfoFromFileAddress(fileAddress, locale);
5133623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                    // Protect against cases of a less-specific dictionary being found, like an
5143623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                    // en dictionary being used for an en_US locale. In this case, the en dictionary
5153623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                    // should be used for en_US but discounted for listing purposes.
516ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                    if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) {
517ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                        continue;
518ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                    }
5193623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                    addOrUpdateDictInfo(dictList, dictionaryInfo);
5203623ad238c3fa76f36cb54de63bd31cf320befb4Jean Chalard                }
521af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            }
522af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        }
523af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard
5243a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        // Retrieve downloaded dictionaries from the unused dictionaries.
5253a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        File[] unusedDictionaryList = getUnusedDictionaryList(context);
5263a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        if (unusedDictionaryList != null) {
5273a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            for (File dictionaryFile : unusedDictionaryList) {
5283a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                String fileName = dictionaryFile.getName();
5293a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                int index = fileName.indexOf(TEMP_DICT_FILE_SUB);
5303a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                if (index == -1) {
5313a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                    continue;
5323a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                }
5333a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                String locale = fileName.substring(0, index);
5343a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                DictionaryInfo dictionaryInfo = createDictionaryInfoForUnCachedFile(
5353a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                        AssetFileAddress.makeFromFile(dictionaryFile),
5363a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                        LocaleUtils.constructLocaleFromString(locale));
5373a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                if (dictionaryInfo != null) {
5383a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                    addOrUpdateDictInfo(dictList, dictionaryInfo);
5393a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik                }
5403a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik            }
5413a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik        }
5423a5de64110eab7ae0b6b1da86b5ce30d5b16bd7aMohammadinamul Sheik
543af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        // Retrieve files from assets
544af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        final Resources resources = context.getResources();
545af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        final AssetManager assets = resources.getAssets();
546af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        for (final String localeString : assets.getLocales()) {
547af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
548af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            final int resourceId =
549af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard                    DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
550af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard                            context.getResources(), locale);
551ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka            if (0 == resourceId) {
552ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                continue;
553ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka            }
554af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            final AssetFileAddress fileAddress =
555af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard                    BinaryDictionaryGetter.loadFallbackResource(context, resourceId);
5566e29d261cb480ef0f0d90867d4582fabce0b9113Mohammadinamul Sheik            final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress,
5576e29d261cb480ef0f0d90867d4582fabce0b9113Mohammadinamul Sheik                    locale);
558af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            // Protect against cases of a less-specific dictionary being found, like an
559af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            // en dictionary being used for an en_US locale. In this case, the en dictionary
560af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            // should be used for en_US but discounted for listing purposes.
56124c282437dd7dd4b135e3410b5459ca83d304524Mohammadinamul Sheik            // TODO: Remove dictionaryInfo == null when the static LMs have the headers.
56224c282437dd7dd4b135e3410b5459ca83d304524Mohammadinamul Sheik            if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) {
563ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                continue;
564ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka            }
565af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard            addOrUpdateDictInfo(dictList, dictionaryInfo);
566af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        }
567af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard
5683bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik        // Generate the dictionary information from  the enabled subtypes. This will not
5693bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik        // overwrite the real records.
5703bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik        RichInputMethodManager.init(context);
5713bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik        List<InputMethodSubtype> enabledSubtypes = RichInputMethodManager
5723bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik                .getInstance().getMyEnabledInputMethodSubtypeList(true);
5733bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik        for (InputMethodSubtype subtype : enabledSubtypes) {
5743bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik            Locale locale = LocaleUtils.constructLocaleFromString(subtype.getLocale());
5753bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik            DictionaryInfo dictionaryInfo = createDictionaryInfoFromLocale(locale);
5763bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik            addOrUpdateDictInfo(dictList, dictionaryInfo);
5773bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik        }
5783bc3bc7971f15438732933cfac0db6e766e6a3e9Mohammadinamul Sheik
579af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard        return dictList;
580af4a7e8c4b2a41e9be48965133ab489cc9484764Jean Chalard    }
58184a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard
5828c889784e2d20bb3ebc1ad869176a791a755ccc6Adrian Velicu    @UsedForTesting
58384a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard    public static boolean looksValidForDictionaryInsertion(final CharSequence text,
58484a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard            final SpacingAndPunctuations spacingAndPunctuations) {
585ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka        if (TextUtils.isEmpty(text)) {
586ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka            return false;
587ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka        }
58884a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        final int length = text.length();
5890f7d881dc72132dfd75c8b4fe61a69fc5cdcd460Mohammadinamul Sheik        if (length > DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH) {
590b7197b705bffb82393e38e225f9082205fb26a23Keisuke Kuroyanagi            return false;
591b7197b705bffb82393e38e225f9082205fb26a23Keisuke Kuroyanagi        }
59284a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        int i = 0;
59384a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        int digitCount = 0;
59484a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        while (i < length) {
59584a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard            final int codePoint = Character.codePointAt(text, i);
59684a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard            final int charCount = Character.charCount(codePoint);
59784a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard            i += charCount;
59884a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard            if (Character.isDigit(codePoint)) {
59984a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard                // Count digits: see below
60084a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard                digitCount += charCount;
60184a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard                continue;
60284a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard            }
603ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka            if (!spacingAndPunctuations.isWordCodePoint(codePoint)) {
604ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka                return false;
605ebe5b42f71bd63973edffbda691b498611326c6fTadashi G. Takaoka            }
60684a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        }
60784a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        // We reject strings entirely comprised of digits to avoid using PIN codes or credit
60884a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        // card numbers. It would come in handy for word prediction though; a good example is
60984a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        // when writing one's address where the street number is usually quite discriminative,
61084a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        // as well as the postal code.
61184a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard        return digitCount < length;
61284a3047e801923bd486b0cff2f9ea0de25d7e3baJean Chalard    }
613d515f134f726c432c0bab5600e7b31ed989fb1b5Jean Chalard}
614