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