diff --git a/app/build.gradle b/app/build.gradle index b9ca2978d..90003e9a7 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,7 +36,8 @@ android { ndkVersion '21.3.6528147' androidResources { - noCompress 'dict' + noCompress 'main.dict' + noCompress 'empty.dict' } } diff --git a/app/src/main/res/raw/main_bg.dict b/app/src/main/assets/dicts/main_bg.dict similarity index 100% rename from app/src/main/res/raw/main_bg.dict rename to app/src/main/assets/dicts/main_bg.dict diff --git a/app/src/main/res/raw/main_bn.dict b/app/src/main/assets/dicts/main_bn.dict similarity index 100% rename from app/src/main/res/raw/main_bn.dict rename to app/src/main/assets/dicts/main_bn.dict diff --git a/app/src/main/res/raw/main_da.dict b/app/src/main/assets/dicts/main_da.dict similarity index 100% rename from app/src/main/res/raw/main_da.dict rename to app/src/main/assets/dicts/main_da.dict diff --git a/app/src/main/res/raw/main_de.dict b/app/src/main/assets/dicts/main_de.dict similarity index 100% rename from app/src/main/res/raw/main_de.dict rename to app/src/main/assets/dicts/main_de.dict diff --git a/app/src/main/res/raw/main_el.dict b/app/src/main/assets/dicts/main_el.dict similarity index 100% rename from app/src/main/res/raw/main_el.dict rename to app/src/main/assets/dicts/main_el.dict diff --git a/app/src/main/res/raw/main_en.dict b/app/src/main/assets/dicts/main_en.dict similarity index 100% rename from app/src/main/res/raw/main_en.dict rename to app/src/main/assets/dicts/main_en.dict diff --git a/app/src/main/res/raw/main_eo.dict b/app/src/main/assets/dicts/main_eo.dict similarity index 100% rename from app/src/main/res/raw/main_eo.dict rename to app/src/main/assets/dicts/main_eo.dict diff --git a/app/src/main/res/raw/main_es.dict b/app/src/main/assets/dicts/main_es.dict similarity index 100% rename from app/src/main/res/raw/main_es.dict rename to app/src/main/assets/dicts/main_es.dict diff --git a/app/src/main/res/raw/main_fr.dict b/app/src/main/assets/dicts/main_fr.dict similarity index 100% rename from app/src/main/res/raw/main_fr.dict rename to app/src/main/assets/dicts/main_fr.dict diff --git a/app/src/main/res/raw/main_hu.dict b/app/src/main/assets/dicts/main_hu.dict similarity index 100% rename from app/src/main/res/raw/main_hu.dict rename to app/src/main/assets/dicts/main_hu.dict diff --git a/app/src/main/res/raw/main_it.dict b/app/src/main/assets/dicts/main_it.dict similarity index 100% rename from app/src/main/res/raw/main_it.dict rename to app/src/main/assets/dicts/main_it.dict diff --git a/app/src/main/res/raw/main_nl.dict b/app/src/main/assets/dicts/main_nl.dict similarity index 100% rename from app/src/main/res/raw/main_nl.dict rename to app/src/main/assets/dicts/main_nl.dict diff --git a/app/src/main/res/raw/main_pl.dict b/app/src/main/assets/dicts/main_pl.dict similarity index 100% rename from app/src/main/res/raw/main_pl.dict rename to app/src/main/assets/dicts/main_pl.dict diff --git a/app/src/main/res/raw/main_pt_br.dict b/app/src/main/assets/dicts/main_pt_br.dict similarity index 100% rename from app/src/main/res/raw/main_pt_br.dict rename to app/src/main/assets/dicts/main_pt_br.dict diff --git a/app/src/main/res/raw/main_pt_pt.dict b/app/src/main/assets/dicts/main_pt_pt.dict similarity index 100% rename from app/src/main/res/raw/main_pt_pt.dict rename to app/src/main/assets/dicts/main_pt_pt.dict diff --git a/app/src/main/res/raw/main_ro.dict b/app/src/main/assets/dicts/main_ro.dict similarity index 100% rename from app/src/main/res/raw/main_ro.dict rename to app/src/main/assets/dicts/main_ro.dict diff --git a/app/src/main/res/raw/main_ru.dict b/app/src/main/assets/dicts/main_ru.dict similarity index 100% rename from app/src/main/res/raw/main_ru.dict rename to app/src/main/assets/dicts/main_ru.dict diff --git a/app/src/main/res/raw/main_sv.dict b/app/src/main/assets/dicts/main_sv.dict similarity index 100% rename from app/src/main/res/raw/main_sv.dict rename to app/src/main/assets/dicts/main_sv.dict diff --git a/app/src/main/res/raw/main_tr.dict b/app/src/main/assets/dicts/main_tr.dict similarity index 100% rename from app/src/main/res/raw/main_tr.dict rename to app/src/main/assets/dicts/main_tr.dict diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java index 08c7026db..38c81f60f 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java @@ -29,7 +29,9 @@ import org.dslul.openboard.inputmethod.latin.utils.BinaryDictionaryUtils; import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.BufferUnderflowException; import java.util.ArrayList; import java.util.HashMap; @@ -62,6 +64,9 @@ final public class BinaryDictionaryGetter { public static final String MAIN_DICTIONARY_CATEGORY = "main"; public static final String ID_CATEGORY_SEPARATOR = ":"; + public static final String MAIN_DICTIONARY_FILE_NAME = MAIN_DICTIONARY_CATEGORY + ".dict"; + public static final String ASSETS_DICTIONARY_FOLDER = "dicts"; + // The key considered to read the version attribute in a dictionary file. private static String VERSION_KEY = "version"; @@ -265,9 +270,16 @@ final public class BinaryDictionaryGetter { } if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) { - final int fallbackResId = - DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale); - final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId); + final File dict = loadDictionaryFromAssets(locale.toString(), context); + final AssetFileAddress fallbackAsset; + if (dict == null) { + // fall back to the old way (maybe remove? will not work if files are compressed) + final int fallbackResId = + DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale); + fallbackAsset = loadFallbackResource(context, fallbackResId); + } else { + fallbackAsset = AssetFileAddress.makeFromFileName(dict.getPath()); + } if (null != fallbackAsset) { fileList.add(fallbackAsset); } @@ -275,4 +287,76 @@ final public class BinaryDictionaryGetter { return fileList; } + + /** + * Returns the best matching main dictionary from assets. + * + * Actually copies the dictionary to cache folder, and then returns that file. This allows + * the dictionaries to be stored in a compressed way, reducing APK size. + * On next load, the dictionary in cache folder is found by getCachedWordLists + * + * Returns null on IO errors or if no matching dictionary is found + */ + public static File loadDictionaryFromAssets(final String locale, final Context context) { + final String[] dictionaryList; + try { + dictionaryList = context.getAssets().list(ASSETS_DICTIONARY_FOLDER); + } catch (IOException e) { + return null; + } + if (null == dictionaryList) return null; + String bestMatchName = null; + int bestMatchLevel = 0; + for (String dictionary : dictionaryList) { + final String dictLocale = + extractLocaleFromAssetsDictionaryFile(dictionary); + if (dictLocale == null) continue; + final int matchLevel = LocaleUtils.getMatchLevel(dictLocale, locale); + if (LocaleUtils.isMatch(matchLevel) && matchLevel > bestMatchLevel) { + bestMatchName = dictionary; + } + } + if (bestMatchName == null) return null; + + // we have a match, now copy contents of the dictionary to "cached" word lists folder + File outfile = new File(DictionaryInfoUtils.getWordListCacheDirectory(context) + + File.separator + extractLocaleFromAssetsDictionaryFile(bestMatchName) + File.separator + + BinaryDictionaryGetter.MAIN_DICTIONARY_FILE_NAME); + File parentFile = outfile.getParentFile(); + if (parentFile == null || (!parentFile.exists() && !parentFile.mkdirs())) { + return null; + } + try { + InputStream in = context.getAssets().open(ASSETS_DICTIONARY_FOLDER + File.separator + bestMatchName); + FileOutputStream out = new FileOutputStream(outfile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.flush(); + return outfile; + } catch (IOException e) { + Log.e(TAG, "exception while looking for locale " + locale, e); + return null; + } + } + + /** + * Returns the locale for a dictionary file name stored in assets. + * + * Assumes file name main_[locale].dict + * + * Returns the locale, or null if file name does not match the pattern + */ + private static String extractLocaleFromAssetsDictionaryFile(final String dictionaryFileName) { + if (dictionaryFileName.startsWith(BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY) + && dictionaryFileName.endsWith(".dict")) { + return dictionaryFileName.substring( + BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.length() + 1, + dictionaryFileName.lastIndexOf('.') + ); + } + return null; + } } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java index 364ecdf9d..f22d2fb3c 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java @@ -151,7 +151,7 @@ public class DictionaryInfoUtils { /** * Helper method to get the top level cache directory. */ - private static String getWordListCacheDirectory(final Context context) { + public static String getWordListCacheDirectory(final Context context) { return context.getFilesDir() + File.separator + "dicts"; } @@ -242,7 +242,7 @@ public class DictionaryInfoUtils { // An id is supposed to be in format category:locale, so splitting on the separator // should yield a 2-elements array if (2 != idArray.length) { - return false; + return id.startsWith(BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY); } return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); }