mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-21 09:21:20 +00:00
allow multilingual typing with an arbitrary number of languages
currently can't set mor than one extra language, will be enabled in language settings re-work
This commit is contained in:
parent
e7e05ca1c2
commit
592f4aab0c
5 changed files with 297 additions and 294 deletions
|
@ -61,6 +61,8 @@ import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.LanguageOnSpacebarUtils;
|
import org.dslul.openboard.inputmethod.latin.utils.LanguageOnSpacebarUtils;
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.TypefaceUtils;
|
import org.dslul.openboard.inputmethod.latin.utils.TypefaceUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
@ -844,19 +846,27 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy
|
||||||
final RichInputMethodSubtype subtype, final int width) {
|
final RichInputMethodSubtype subtype, final int width) {
|
||||||
// Choose appropriate language name to fit into the width.
|
// Choose appropriate language name to fit into the width.
|
||||||
|
|
||||||
final Locale secondaryLocale = Settings.getInstance().getCurrent().mSecondaryLocale;
|
final List<Locale> secondaryLocales = Settings.getInstance().getCurrent().mSecondaryLocales;
|
||||||
if (secondaryLocale != null
|
// avoid showing same language twice
|
||||||
// avoid showing same language twice
|
final List<Locale> secondaryLocalesToUse = withoutDuplicateLanguages(secondaryLocales, subtype.getLocale().getLanguage());
|
||||||
&& !secondaryLocale.getLanguage().equals(subtype.getLocale().getLanguage())
|
if (secondaryLocalesToUse.size() > 0) {
|
||||||
) {
|
StringBuilder sb = new StringBuilder(subtype.getMiddleDisplayName());
|
||||||
final Locale displayLocale = getResources().getConfiguration().locale;
|
final Locale displayLocale = getResources().getConfiguration().locale;
|
||||||
final String full = subtype.getMiddleDisplayName() + " - " +
|
for (Locale locale : secondaryLocales) {
|
||||||
secondaryLocale.getDisplayLanguage(displayLocale);
|
sb.append(" - ");
|
||||||
|
sb.append(locale.getDisplayLanguage(displayLocale));
|
||||||
|
}
|
||||||
|
final String full = sb.toString();
|
||||||
if (fitsTextIntoWidth(width, full, paint)) {
|
if (fitsTextIntoWidth(width, full, paint)) {
|
||||||
return full;
|
return full;
|
||||||
}
|
}
|
||||||
final String middle = subtype.getLocale().getLanguage().toUpperCase(displayLocale) +
|
sb.setLength(0);
|
||||||
" - " + secondaryLocale.getLanguage().toUpperCase(displayLocale);
|
sb.append(subtype.getLocale().getLanguage().toUpperCase(displayLocale));
|
||||||
|
for (Locale locale : secondaryLocales) {
|
||||||
|
sb.append(" - ");
|
||||||
|
sb.append(locale.getLanguage().toUpperCase(displayLocale));
|
||||||
|
}
|
||||||
|
final String middle = sb.toString();
|
||||||
if (fitsTextIntoWidth(width, middle, paint)) {
|
if (fitsTextIntoWidth(width, middle, paint)) {
|
||||||
return middle;
|
return middle;
|
||||||
}
|
}
|
||||||
|
@ -877,6 +887,23 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Locale> withoutDuplicateLanguages(List<Locale> locales, String mainLanguage) {
|
||||||
|
ArrayList<String> languages = new ArrayList<String>() {{ add(mainLanguage); }};
|
||||||
|
ArrayList<Locale> newLocales = new ArrayList<>();
|
||||||
|
for (Locale locale : locales) {
|
||||||
|
boolean keep = true;
|
||||||
|
for (String language : languages) {
|
||||||
|
if (locale.getLanguage().equals(language))
|
||||||
|
keep = false;
|
||||||
|
}
|
||||||
|
if (!keep)
|
||||||
|
continue;
|
||||||
|
languages.add(locale.getLanguage());
|
||||||
|
newLocales.add(locale);
|
||||||
|
}
|
||||||
|
return newLocales;
|
||||||
|
}
|
||||||
|
|
||||||
private void drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint) {
|
private void drawLanguageOnSpacebar(final Key key, final Canvas canvas, final Paint paint) {
|
||||||
final Keyboard keyboard = getKeyboard();
|
final Keyboard keyboard = getKeyboard();
|
||||||
if (keyboard == null) {
|
if (keyboard == null) {
|
||||||
|
|
|
@ -76,8 +76,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
// dictionary.
|
// dictionary.
|
||||||
private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
|
private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
|
||||||
|
|
||||||
private DictionaryGroup mDictionaryGroup = new DictionaryGroup();
|
private ArrayList<DictionaryGroup> mDictionaryGroups = new ArrayList<DictionaryGroup>() {{ add(new DictionaryGroup()); }};
|
||||||
private DictionaryGroup mSecondaryDictionaryGroup = new DictionaryGroup();
|
|
||||||
private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
|
private volatile CountDownLatch mLatchForWaitingLoadingMainDictionaries = new CountDownLatch(0);
|
||||||
// To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
|
// To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
|
@ -95,9 +94,9 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
|
private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
|
||||||
new Class[] { Context.class, Locale.class, File.class, String.class, String.class };
|
new Class[] { Context.class, Locale.class, File.class, String.class, String.class };
|
||||||
|
|
||||||
// these caches are never even set, as the corresponding functions are not called...
|
// todo: these caches are never even set, as the corresponding functions are not called...
|
||||||
// and even if they were set, one is only written, but never read, and the other one
|
// and even if they were set, one is only written, but never read, and the other one
|
||||||
// is only read and thus empty and useless
|
// is only read and thus empty and useless -> why? seems they are not needed anyway
|
||||||
private LruCache<String, Boolean> mValidSpellingWordReadCache;
|
private LruCache<String, Boolean> mValidSpellingWordReadCache;
|
||||||
private LruCache<String, Boolean> mValidSpellingWordWriteCache;
|
private LruCache<String, Boolean> mValidSpellingWordWriteCache;
|
||||||
|
|
||||||
|
@ -113,12 +112,15 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isForLocale(final Locale locale) {
|
public boolean isForLocale(final Locale locale) {
|
||||||
return locale != null && locale.equals(mDictionaryGroup.mLocale);
|
return locale != null && locale.equals(mDictionaryGroups.get(0).mLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasLocale(final Locale locale) {
|
private boolean hasLocale(final Locale locale) {
|
||||||
return locale != null && (locale.equals(mDictionaryGroup.mLocale) ||
|
if (locale == null) return false;
|
||||||
(mSecondaryDictionaryGroup != null && locale.equals(mSecondaryDictionaryGroup.mLocale)));
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
|
if (locale.equals(dictionaryGroup.mLocale)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,7 +129,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
* @param account the account to test against.
|
* @param account the account to test against.
|
||||||
*/
|
*/
|
||||||
public boolean isForAccount(@Nullable final String account) {
|
public boolean isForAccount(@Nullable final String account) {
|
||||||
return TextUtils.equals(mDictionaryGroup.mAccount, account);
|
return TextUtils.equals(mDictionaryGroups.get(0).mAccount, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -275,30 +277,27 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinishInput(Context context) {
|
public void onFinishInput(Context context) {
|
||||||
for (final String dictType : ALL_DICTIONARY_TYPES) {
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
Dictionary dict = mDictionaryGroup.getDict(dictType);
|
|
||||||
if (dict != null) dict.onFinishInput();
|
|
||||||
}
|
|
||||||
if (mSecondaryDictionaryGroup != null)
|
|
||||||
for (final String dictType : ALL_DICTIONARY_TYPES) {
|
for (final String dictType : ALL_DICTIONARY_TYPES) {
|
||||||
Dictionary dict = mSecondaryDictionaryGroup.getDict(dictType);
|
Dictionary dict = dictionaryGroup.getDict(dictType);
|
||||||
if (dict != null) dict.onFinishInput();
|
if (dict != null) dict.onFinishInput();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return mDictionaryGroup.mLocale != null;
|
return mDictionaryGroups.get(0).mLocale != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Locale getLocale() {
|
public Locale getLocale() {
|
||||||
return mDictionaryGroup.mLocale;
|
return mDictionaryGroups.get(0).mLocale;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean usesContacts() {
|
public boolean usesContacts() {
|
||||||
return mDictionaryGroup.getSubDict(Dictionary.TYPE_CONTACTS) != null;
|
return mDictionaryGroups.get(0).getSubDict(Dictionary.TYPE_CONTACTS) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -329,17 +328,19 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
static DictionaryGroup findDictionaryGroupWithLocale(final DictionaryGroup dictionaryGroup,
|
static DictionaryGroup findDictionaryGroupWithLocale(final List<DictionaryGroup> dictionaryGroups,
|
||||||
final Locale locale) {
|
final Locale locale) {
|
||||||
if (dictionaryGroup == null) return null;
|
if (dictionaryGroups == null) return null;
|
||||||
return locale.equals(dictionaryGroup.mLocale) ? dictionaryGroup : null;
|
for (DictionaryGroup dictionaryGroup : dictionaryGroups) {
|
||||||
|
if (locale == null && dictionaryGroup.mLocale == null)
|
||||||
|
return dictionaryGroup;
|
||||||
|
if (locale != null && locale.equals(dictionaryGroup.mLocale))
|
||||||
|
return dictionaryGroup;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// original
|
||||||
// TODO: what if secondary locale changes, but main remains same?
|
|
||||||
// current reset doesn't consider this (not here, and not in other places where locales
|
|
||||||
// are checked against current locale)
|
|
||||||
// but that actually shouldn't happen anyway...
|
|
||||||
public void resetDictionaries(
|
public void resetDictionaries(
|
||||||
final Context context,
|
final Context context,
|
||||||
final Locale newLocale,
|
final Locale newLocale,
|
||||||
|
@ -353,6 +354,10 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
// TODO: Make subDictTypesToUse configurable by resource or a static final list.
|
// TODO: Make subDictTypesToUse configurable by resource or a static final list.
|
||||||
final HashSet<String> subDictTypesToUse = new HashSet<>();
|
final HashSet<String> subDictTypesToUse = new HashSet<>();
|
||||||
subDictTypesToUse.add(Dictionary.TYPE_USER);
|
subDictTypesToUse.add(Dictionary.TYPE_USER);
|
||||||
|
final List<Locale> allLocales = new ArrayList<Locale>() {{
|
||||||
|
add(newLocale);
|
||||||
|
addAll(Settings.getInstance().getCurrent().mSecondaryLocales);
|
||||||
|
}};
|
||||||
|
|
||||||
// Do not use contacts dictionary if we do not have permissions to read contacts.
|
// Do not use contacts dictionary if we do not have permissions to read contacts.
|
||||||
final boolean contactsPermissionGranted = PermissionsUtil.checkAllPermissionsGranted(
|
final boolean contactsPermissionGranted = PermissionsUtil.checkAllPermissionsGranted(
|
||||||
|
@ -364,121 +369,83 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
|
subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather all dictionaries. We'll remove them from the list to clean up later.
|
// Gather all dictionaries by locale. We may remove some from the list to clean up later.
|
||||||
final ArrayList<String> dictTypeForLocale = new ArrayList<>();
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
existingDictionariesToCleanup.put(newLocale, dictTypeForLocale);
|
final ArrayList<String> dictTypeForLocale = new ArrayList<>();
|
||||||
final DictionaryGroup currentDictionaryGroupForLocale =
|
existingDictionariesToCleanup.put(dictionaryGroup.mLocale, dictTypeForLocale);
|
||||||
findDictionaryGroupWithLocale(mDictionaryGroup, newLocale);
|
|
||||||
if (currentDictionaryGroupForLocale != null) {
|
|
||||||
for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
|
for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
|
||||||
if (currentDictionaryGroupForLocale.hasDict(dictType, account)) {
|
if (dictionaryGroup.hasDict(dictType, account)) {
|
||||||
dictTypeForLocale.add(dictType);
|
dictTypeForLocale.add(dictType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
|
if (dictionaryGroup.hasDict(Dictionary.TYPE_MAIN, account)) {
|
||||||
dictTypeForLocale.add(Dictionary.TYPE_MAIN);
|
dictTypeForLocale.add(Dictionary.TYPE_MAIN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final DictionaryGroup dictionaryGroupForLocale =
|
// create new dictionary groups and remove dictionaries to re-use from existingDictionariesToCleanup
|
||||||
findDictionaryGroupWithLocale(mDictionaryGroup, newLocale);
|
final ArrayList<DictionaryGroup> newDictionaryGroups = new ArrayList<>(allLocales.size());
|
||||||
final ArrayList<String> dictTypesToCleanupForLocale =
|
for (Locale locale : allLocales) {
|
||||||
existingDictionariesToCleanup.get(newLocale);
|
// get existing dictionary group for new locale
|
||||||
final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale);
|
final DictionaryGroup oldDictionaryGroupForLocale =
|
||||||
|
findDictionaryGroupWithLocale(mDictionaryGroups, locale);
|
||||||
|
final ArrayList<String> dictTypesToCleanupForLocale =
|
||||||
|
existingDictionariesToCleanup.get(locale);
|
||||||
|
final boolean noExistingDictsForThisLocale = (null == oldDictionaryGroupForLocale);
|
||||||
|
|
||||||
final Dictionary mainDict;
|
// create new or re-use already loaded main dict
|
||||||
if (forceReloadMainDictionary || noExistingDictsForThisLocale
|
final Dictionary mainDict;
|
||||||
|| !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
|
if (forceReloadMainDictionary || noExistingDictsForThisLocale
|
||||||
mainDict = null;
|
|| !oldDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
|
||||||
} else {
|
mainDict = null;
|
||||||
mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN);
|
|
||||||
dictTypesToCleanupForLocale.remove(Dictionary.TYPE_MAIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
|
|
||||||
for (final String subDictType : subDictTypesToUse) {
|
|
||||||
final ExpandableBinaryDictionary subDict;
|
|
||||||
if (noExistingDictsForThisLocale
|
|
||||||
|| !dictionaryGroupForLocale.hasDict(subDictType, account)) {
|
|
||||||
// Create a new dictionary.
|
|
||||||
subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */,
|
|
||||||
dictNamePrefix, account);
|
|
||||||
} else {
|
} else {
|
||||||
// Reuse the existing dictionary, and don't close it at the end
|
mainDict = oldDictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN);
|
||||||
subDict = dictionaryGroupForLocale.getSubDict(subDictType);
|
dictTypesToCleanupForLocale.remove(Dictionary.TYPE_MAIN);
|
||||||
dictTypesToCleanupForLocale.remove(subDictType);
|
|
||||||
}
|
}
|
||||||
subDicts.put(subDictType, subDict);
|
|
||||||
}
|
|
||||||
DictionaryGroup newDictionaryGroup =
|
|
||||||
new DictionaryGroup(newLocale, mainDict, account, subDicts);
|
|
||||||
|
|
||||||
// create / load secondary dictionary
|
// create new or re-use already loaded sub-dicts
|
||||||
final Locale secondaryLocale = Settings.getInstance().getCurrent().mSecondaryLocale;
|
final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
|
||||||
final DictionaryGroup newSecondaryDictionaryGroup;
|
|
||||||
final Map<String, ExpandableBinaryDictionary> secondarySubDicts = new HashMap<>();
|
|
||||||
|
|
||||||
if (secondaryLocale != null &&
|
|
||||||
ScriptUtils.getScriptFromSpellCheckerLocale(secondaryLocale) == ScriptUtils.getScriptFromSpellCheckerLocale(newLocale)) {
|
|
||||||
final ArrayList<String> dictTypesToCleanUp = new ArrayList<>();
|
|
||||||
for (final String dictType : ALL_DICTIONARY_TYPES) {
|
|
||||||
if (mSecondaryDictionaryGroup != null && mSecondaryDictionaryGroup.hasDict(dictType, account)) {
|
|
||||||
dictTypesToCleanUp.add(dictType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (final String subDictType : subDictTypesToUse) {
|
for (final String subDictType : subDictTypesToUse) {
|
||||||
final ExpandableBinaryDictionary subDict =
|
final ExpandableBinaryDictionary subDict;
|
||||||
getSubDict(subDictType, context, secondaryLocale, null, dictNamePrefix, account);
|
if (noExistingDictsForThisLocale
|
||||||
secondarySubDicts.put(subDictType, subDict);
|
|| !oldDictionaryGroupForLocale.hasDict(subDictType, account)) {
|
||||||
dictTypesToCleanUp.remove(subDictType);
|
// Create a new dictionary.
|
||||||
|
subDict = getSubDict(subDictType, context, locale, null /* dictFile */, dictNamePrefix, account);
|
||||||
|
} else {
|
||||||
|
// Reuse the existing dictionary, and don't close it at the end
|
||||||
|
subDict = oldDictionaryGroupForLocale.getSubDict(subDictType);
|
||||||
|
dictTypesToCleanupForLocale.remove(subDictType);
|
||||||
|
}
|
||||||
|
subDicts.put(subDictType, subDict);
|
||||||
}
|
}
|
||||||
final Dictionary secondaryMainDict;
|
DictionaryGroup newDictGroup = new DictionaryGroup(locale, mainDict, account, subDicts);
|
||||||
if (forceReloadMainDictionary || findDictionaryGroupWithLocale(mSecondaryDictionaryGroup, secondaryLocale) == null
|
newDictionaryGroups.add(newDictGroup);
|
||||||
|| !mSecondaryDictionaryGroup.hasDict(Dictionary.TYPE_MAIN, account)) {
|
|
||||||
secondaryMainDict = null;
|
|
||||||
} else {
|
|
||||||
if (mSecondaryDictionaryGroup == null)
|
|
||||||
secondaryMainDict = null;
|
|
||||||
else
|
|
||||||
secondaryMainDict = mSecondaryDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
|
|
||||||
dictTypesToCleanUp.remove(Dictionary.TYPE_MAIN);
|
|
||||||
}
|
|
||||||
newSecondaryDictionaryGroup = new DictionaryGroup(secondaryLocale, secondaryMainDict, account, secondarySubDicts);
|
|
||||||
|
|
||||||
// do the cleanup like for main dict: look like this is for removing dictionaries
|
// load blacklist
|
||||||
// after user changed enabled types (e.g. disable personalized suggestions)
|
if (noExistingDictsForThisLocale) {
|
||||||
existingDictionariesToCleanup.put(secondaryLocale, dictTypesToCleanUp);
|
newDictGroup.blacklistFileName = context.getFilesDir().getAbsolutePath() + File.separator + "blacklists" + File.separator + locale.toString().toLowerCase(Locale.ENGLISH) + ".txt";
|
||||||
} else {
|
if (!new File(newDictGroup.blacklistFileName).exists())
|
||||||
newSecondaryDictionaryGroup = null;
|
new File(context.getFilesDir().getAbsolutePath() + File.separator + "blacklists").mkdirs();
|
||||||
|
newDictGroup.blacklist.addAll(readBlacklistFile(newDictGroup.blacklistFileName));
|
||||||
|
} else {
|
||||||
|
// re-use if possible
|
||||||
|
newDictGroup.blacklistFileName = oldDictionaryGroupForLocale.blacklistFileName;
|
||||||
|
newDictGroup.blacklist.addAll(oldDictionaryGroupForLocale.blacklist);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Replace Dictionaries.
|
// Replace Dictionaries.
|
||||||
final DictionaryGroup oldDictionaryGroup;
|
final List<DictionaryGroup> oldDictionaryGroups;
|
||||||
final DictionaryGroup oldSecondaryDictionaryGroup;
|
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
oldDictionaryGroup = mDictionaryGroup;
|
oldDictionaryGroups = mDictionaryGroups;
|
||||||
mDictionaryGroup = newDictionaryGroup;
|
mDictionaryGroups = newDictionaryGroups;
|
||||||
oldSecondaryDictionaryGroup = mSecondaryDictionaryGroup;
|
|
||||||
mSecondaryDictionaryGroup = newSecondaryDictionaryGroup;
|
|
||||||
if (hasAtLeastOneUninitializedMainDictionary()) {
|
if (hasAtLeastOneUninitializedMainDictionary()) {
|
||||||
asyncReloadUninitializedMainDictionaries(context, newLocale,
|
asyncReloadUninitializedMainDictionaries(context, allLocales, listener);
|
||||||
mSecondaryDictionaryGroup == null ? null : secondaryLocale, listener);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// load blacklists
|
|
||||||
mDictionaryGroup.blacklistFileName = context.getFilesDir().getAbsolutePath() + File.separator + "blacklists" + File.separator + newLocale.toString().toLowerCase(Locale.ENGLISH) + ".txt";
|
|
||||||
if (!new File(mDictionaryGroup.blacklistFileName).exists())
|
|
||||||
new File(context.getFilesDir().getAbsolutePath() + File.separator + "blacklists").mkdirs();
|
|
||||||
mDictionaryGroup.blacklist.addAll(readBlacklistFile(mDictionaryGroup.blacklistFileName));
|
|
||||||
|
|
||||||
if (mSecondaryDictionaryGroup != null) {
|
|
||||||
mSecondaryDictionaryGroup.blacklistFileName = context.getFilesDir().getAbsolutePath() + File.separator + "blacklists" + File.separator + secondaryLocale.toString().toLowerCase(Locale.ENGLISH) + ".txt";
|
|
||||||
if (!new File(mSecondaryDictionaryGroup.blacklistFileName).exists())
|
|
||||||
new File(context.getFilesDir().getAbsolutePath() + File.separator + "blacklists").mkdirs();
|
|
||||||
mSecondaryDictionaryGroup.blacklist.addAll(readBlacklistFile(mSecondaryDictionaryGroup.blacklistFileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
|
listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
|
||||||
}
|
}
|
||||||
|
@ -487,13 +454,8 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
for (final Locale localeToCleanUp : existingDictionariesToCleanup.keySet()) {
|
for (final Locale localeToCleanUp : existingDictionariesToCleanup.keySet()) {
|
||||||
final ArrayList<String> dictTypesToCleanUp =
|
final ArrayList<String> dictTypesToCleanUp =
|
||||||
existingDictionariesToCleanup.get(localeToCleanUp);
|
existingDictionariesToCleanup.get(localeToCleanUp);
|
||||||
DictionaryGroup dictionarySetToCleanup =
|
final DictionaryGroup dictionarySetToCleanup =
|
||||||
findDictionaryGroupWithLocale(oldDictionaryGroup, localeToCleanUp);
|
findDictionaryGroupWithLocale(oldDictionaryGroups, localeToCleanUp);
|
||||||
if (dictionarySetToCleanup == null)
|
|
||||||
dictionarySetToCleanup =
|
|
||||||
findDictionaryGroupWithLocale(oldSecondaryDictionaryGroup, localeToCleanUp);
|
|
||||||
if (dictionarySetToCleanup == null)
|
|
||||||
continue;
|
|
||||||
for (final String dictType : dictTypesToCleanUp) {
|
for (final String dictType : dictTypesToCleanUp) {
|
||||||
dictionarySetToCleanup.closeDict(dictType);
|
dictionarySetToCleanup.closeDict(dictType);
|
||||||
}
|
}
|
||||||
|
@ -505,53 +467,52 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void asyncReloadUninitializedMainDictionaries(final Context context,
|
private void asyncReloadUninitializedMainDictionaries(final Context context,
|
||||||
final Locale locale, final Locale secondaryLocale, final DictionaryInitializationListener listener) {
|
final List<Locale> locales, final DictionaryInitializationListener listener) {
|
||||||
final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
|
final CountDownLatch latchForWaitingLoadingMainDictionary = new CountDownLatch(1);
|
||||||
mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
|
mLatchForWaitingLoadingMainDictionaries = latchForWaitingLoadingMainDictionary;
|
||||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
|
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
doReloadUninitializedMainDictionaries(
|
doReloadUninitializedMainDictionaries(
|
||||||
context, locale, secondaryLocale, listener, latchForWaitingLoadingMainDictionary);
|
context, locales, listener, latchForWaitingLoadingMainDictionary);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void doReloadUninitializedMainDictionaries(final Context context, final Locale locale,
|
void doReloadUninitializedMainDictionaries(final Context context, final List<Locale> locales,
|
||||||
final Locale secondaryLocale, final DictionaryInitializationListener listener,
|
final DictionaryInitializationListener listener,
|
||||||
final CountDownLatch latchForWaitingLoadingMainDictionary) {
|
final CountDownLatch latchForWaitingLoadingMainDictionary) {
|
||||||
final DictionaryGroup dictionaryGroup =
|
final Dictionary[] mainDicts = new Dictionary[locales.size()];
|
||||||
findDictionaryGroupWithLocale(mDictionaryGroup, locale);
|
final ArrayList<DictionaryGroup> dictionaryGroups = new ArrayList<>();
|
||||||
if (null == dictionaryGroup) {
|
for (int i = 0; i < locales.size(); i++) {
|
||||||
// This should never happen, but better safe than crashy
|
Locale locale = locales.get(i);
|
||||||
Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
|
DictionaryGroup dictionaryGroup = findDictionaryGroupWithLocale(mDictionaryGroups, locale);
|
||||||
return;
|
if (null == dictionaryGroup) {
|
||||||
|
// This should never happen, but better safe than crashy
|
||||||
|
Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dictionaryGroups.add(dictionaryGroup);
|
||||||
|
// do nothing if main dict already initialized
|
||||||
|
if (dictionaryGroup.mMainDict != null && dictionaryGroup.mMainDict.isInitialized()) {
|
||||||
|
mainDicts[i] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mainDicts[i] = DictionaryFactory.createMainDictionaryFromManager(context, dictionaryGroup.mLocale);
|
||||||
}
|
}
|
||||||
final Dictionary mainDict =
|
|
||||||
DictionaryFactory.createMainDictionaryFromManager(context, locale);
|
|
||||||
|
|
||||||
final DictionaryGroup secondaryDictionaryGroup;
|
|
||||||
if (secondaryLocale == null)
|
|
||||||
secondaryDictionaryGroup = null;
|
|
||||||
else
|
|
||||||
secondaryDictionaryGroup = findDictionaryGroupWithLocale(mSecondaryDictionaryGroup, secondaryLocale);
|
|
||||||
final Dictionary secondaryMainDict;
|
|
||||||
if (secondaryLocale == null)
|
|
||||||
secondaryMainDict = null;
|
|
||||||
else
|
|
||||||
secondaryMainDict = DictionaryFactory.createMainDictionaryFromManager(context, secondaryLocale);
|
|
||||||
|
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (locale.equals(dictionaryGroup.mLocale)) {
|
for (int i = 0; i < locales.size(); i++) {
|
||||||
dictionaryGroup.setMainDict(mainDict);
|
final Locale locale = locales.get(i);
|
||||||
} else {
|
if (mainDicts[i] == null)
|
||||||
// Dictionary facilitator has been reset for another locale.
|
continue;
|
||||||
mainDict.close();
|
if (locale.equals(dictionaryGroups.get(i).mLocale)) {
|
||||||
|
dictionaryGroups.get(i).setMainDict(mainDicts[i]);
|
||||||
|
} else {
|
||||||
|
// Dictionary facilitator has been reset for another locale.
|
||||||
|
mainDicts[i].close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (secondaryDictionaryGroup != null && secondaryLocale.equals(secondaryDictionaryGroup.mLocale))
|
|
||||||
secondaryDictionaryGroup.setMainDict(secondaryMainDict);
|
|
||||||
else if (secondaryMainDict != null)
|
|
||||||
secondaryMainDict.close();
|
|
||||||
}
|
}
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
|
listener.onUpdateMainDictionaryAvailability(hasAtLeastOneInitializedMainDictionary());
|
||||||
|
@ -587,46 +548,45 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
subDicts.put(dictType, dict);
|
subDicts.put(dictType, dict);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mDictionaryGroup = new DictionaryGroup(locale, mainDictionary, account, subDicts);
|
mDictionaryGroups.clear();
|
||||||
|
mDictionaryGroups.add(new DictionaryGroup(locale, mainDictionary, account, subDicts));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeDictionaries() {
|
public void closeDictionaries() {
|
||||||
final DictionaryGroup mainDictionaryGroupToClose;
|
final ArrayList<DictionaryGroup> dictionaryGroupsToClose;
|
||||||
final DictionaryGroup secondaryDictionaryGroupToClose;
|
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mainDictionaryGroupToClose = mDictionaryGroup;
|
dictionaryGroupsToClose = new ArrayList<>(mDictionaryGroups);
|
||||||
secondaryDictionaryGroupToClose = mSecondaryDictionaryGroup;
|
mDictionaryGroups.clear();
|
||||||
mDictionaryGroup = new DictionaryGroup();
|
mDictionaryGroups.add(new DictionaryGroup());
|
||||||
if (mSecondaryDictionaryGroup != null)
|
|
||||||
mSecondaryDictionaryGroup = new DictionaryGroup();
|
|
||||||
}
|
}
|
||||||
for (final String dictType : ALL_DICTIONARY_TYPES) {
|
for (DictionaryGroup dictionaryGroup : dictionaryGroupsToClose) {
|
||||||
mainDictionaryGroupToClose.closeDict(dictType);
|
for (final String dictType : ALL_DICTIONARY_TYPES) {
|
||||||
if (secondaryDictionaryGroupToClose != null)
|
dictionaryGroup.closeDict(dictType);
|
||||||
secondaryDictionaryGroupToClose.closeDict(dictType);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UsedForTesting
|
@UsedForTesting
|
||||||
public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
|
public ExpandableBinaryDictionary getSubDictForTesting(final String dictName) {
|
||||||
return mDictionaryGroup.getSubDict(dictName);
|
return mDictionaryGroups.get(0).getSubDict(dictName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The main dictionaries are loaded asynchronously. Don't cache the return value
|
// The main dictionaries are loaded asynchronously. Don't cache the return value
|
||||||
// of these methods.
|
// of these methods.
|
||||||
public boolean hasAtLeastOneInitializedMainDictionary() {
|
public boolean hasAtLeastOneInitializedMainDictionary() {
|
||||||
final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
return mainDict != null && mainDict.isInitialized();
|
final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
|
||||||
|
if (mainDict != null && mainDict.isInitialized()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasAtLeastOneUninitializedMainDictionary() {
|
public boolean hasAtLeastOneUninitializedMainDictionary() {
|
||||||
final Dictionary mainDict = mDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
if (mSecondaryDictionaryGroup != null) {
|
final Dictionary mainDict = dictionaryGroup.getDict(Dictionary.TYPE_MAIN);
|
||||||
final Dictionary secondaryDict = mSecondaryDictionaryGroup.getDict(Dictionary.TYPE_MAIN);
|
if (mainDict == null || !mainDict.isInitialized()) return true;
|
||||||
if (secondaryDict == null || !secondaryDict.isInitialized())
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return mainDict == null || !mainDict.isInitialized();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
|
public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
|
||||||
|
@ -638,7 +598,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
|
public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
waitForLoadingMainDictionaries(timeout, unit);
|
waitForLoadingMainDictionaries(timeout, unit);
|
||||||
for (final ExpandableBinaryDictionary dict : mDictionaryGroup.mSubDictMap.values()) {
|
for (final ExpandableBinaryDictionary dict : mDictionaryGroups.get(0).mSubDictMap.values()) {
|
||||||
dict.waitAllTasksForTests();
|
dict.waitAllTasksForTests();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -652,36 +612,33 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
|
|
||||||
final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
|
final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
|
||||||
|
|
||||||
// increase / decrease confidence if we have a secondary dictionary group
|
// increase / decrease confidence if we have more than one dictionary group
|
||||||
Boolean validMainWord = null;
|
boolean[] validWordForDictionary; // store results to avoid unnecessary duplicate lookups
|
||||||
Boolean validSecondaryWord = null;
|
if (mDictionaryGroups.size() > 1 && words.length == 1) {
|
||||||
if (mSecondaryDictionaryGroup != null && words.length == 1) {
|
validWordForDictionary = new boolean[mDictionaryGroups.size()];
|
||||||
// if suggestion was auto-capitalized, check against both the suggestion and the de-capitalized suggestion
|
// if suggestion was auto-capitalized, check against both the suggestion and the de-capitalized suggestion
|
||||||
final String decapitalizedSuggestion;
|
final String decapitalizedSuggestion;
|
||||||
if (wasAutoCapitalized)
|
if (wasAutoCapitalized)
|
||||||
decapitalizedSuggestion = suggestion.substring(0, 1).toLowerCase() + suggestion.substring(1);
|
decapitalizedSuggestion = suggestion.substring(0, 1).toLowerCase() + suggestion.substring(1);
|
||||||
else
|
else
|
||||||
decapitalizedSuggestion = suggestion;
|
decapitalizedSuggestion = suggestion;
|
||||||
validMainWord = isValidWord(suggestion, ALL_DICTIONARY_TYPES, mDictionaryGroup);
|
for (int i = 0; i < mDictionaryGroups.size(); i ++) {
|
||||||
if ((wasAutoCapitalized && isValidWord(decapitalizedSuggestion, ALL_DICTIONARY_TYPES, mDictionaryGroup))
|
final DictionaryGroup dictionaryGroup = mDictionaryGroups.get(i);
|
||||||
|| validMainWord)
|
final boolean isValidWord = isValidWord(suggestion, ALL_DICTIONARY_TYPES, dictionaryGroup);
|
||||||
mDictionaryGroup.increaseConfidence();
|
if (isValidWord || (wasAutoCapitalized && isValidWord(decapitalizedSuggestion, ALL_DICTIONARY_TYPES, dictionaryGroup)))
|
||||||
else mDictionaryGroup.decreaseConfidence();
|
dictionaryGroup.increaseConfidence();
|
||||||
validSecondaryWord = isValidWord(suggestion, ALL_DICTIONARY_TYPES, mSecondaryDictionaryGroup);
|
else dictionaryGroup.decreaseConfidence();
|
||||||
if ((wasAutoCapitalized && isValidWord(decapitalizedSuggestion, ALL_DICTIONARY_TYPES, mSecondaryDictionaryGroup))
|
validWordForDictionary[i] = isValidWord;
|
||||||
|| validSecondaryWord)
|
}
|
||||||
mSecondaryDictionaryGroup.increaseConfidence();
|
} else
|
||||||
else mSecondaryDictionaryGroup.decreaseConfidence();
|
validWordForDictionary = null;
|
||||||
}
|
|
||||||
|
|
||||||
// add word to user dictionary if it is in no other dictionary except user history dictionary
|
// add word to user dictionary if it is in no other dictionary except user history dictionary,
|
||||||
// reasoning: typing the same word again -> we probably want it in some dictionary permanently
|
// reasoning: typing the same word again -> we probably want it in some dictionary permanently
|
||||||
// we need a clearly preferred group to assign it to the correct language (in most cases at least...)
|
if (mDictionaryGroups.get(0).hasDict(Dictionary.TYPE_USER_HISTORY, mDictionaryGroups.get(0).mAccount) // require personalized suggestions to be on
|
||||||
if (mDictionaryGroup.hasDict(Dictionary.TYPE_USER_HISTORY, mDictionaryGroup.mAccount) // disable if personalized suggestions are off
|
&& Settings.getInstance().getCurrent().mAddToPersonalDictionary // ...and the setting
|
||||||
&& Settings.getInstance().getCurrent().mAddToPersonalDictionary
|
|
||||||
&& (mSecondaryDictionaryGroup == null || mDictionaryGroup.mConfidence != mSecondaryDictionaryGroup.mConfidence)
|
|
||||||
&& !wasAutoCapitalized && words.length == 1) {
|
&& !wasAutoCapitalized && words.length == 1) {
|
||||||
addToPersonalDictionaryIfInvalidButInHistory(suggestion, validMainWord, validSecondaryWord);
|
addToPersonalDictionaryIfInvalidButInHistory(suggestion, validWordForDictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
NgramContext ngramContextForCurrentWord = ngramContext;
|
NgramContext ngramContextForCurrentWord = ngramContext;
|
||||||
|
@ -695,35 +652,44 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
ngramContextForCurrentWord =
|
ngramContextForCurrentWord =
|
||||||
ngramContextForCurrentWord.getNextNgramContext(new WordInfo(currentWord));
|
ngramContextForCurrentWord.getNextNgramContext(new WordInfo(currentWord));
|
||||||
|
|
||||||
// remove entered words from blacklist
|
// remove manually entered blacklisted words from blacklist
|
||||||
if (mDictionaryGroup.blacklist.remove(currentWord))
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
removeWordFromBlacklistFile(currentWord, mDictionaryGroup.blacklistFileName);
|
if (dictionaryGroup.blacklist.remove(currentWord))
|
||||||
if (mSecondaryDictionaryGroup != null && mSecondaryDictionaryGroup.blacklist.remove(currentWord))
|
removeWordFromBlacklistFile(currentWord, dictionaryGroup.blacklistFileName);
|
||||||
removeWordFromBlacklistFile(currentWord, mSecondaryDictionaryGroup.blacklistFileName);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// main and secondary isValid provided to avoid duplicate lookups
|
// main and secondary isValid provided to avoid duplicate lookups
|
||||||
private void addToPersonalDictionaryIfInvalidButInHistory(String suggestion, Boolean validMainWord, Boolean validSecondaryWord) {
|
private void addToPersonalDictionaryIfInvalidButInHistory(String suggestion, boolean[] validWordForDictionary) {
|
||||||
|
// we need one clearly preferred group to assign it to the correct language
|
||||||
|
int highestGroup = -1;
|
||||||
|
int highestGroupConfidence = -1;
|
||||||
|
for (int i = 0; i < mDictionaryGroups.size(); i ++) {
|
||||||
|
final DictionaryGroup dictionaryGroup = mDictionaryGroups.get(i);
|
||||||
|
if (dictionaryGroup.mConfidence > highestGroupConfidence) {
|
||||||
|
highestGroup = i;
|
||||||
|
highestGroupConfidence = dictionaryGroup.mConfidence;
|
||||||
|
} else if (dictionaryGroup.mConfidence == highestGroupConfidence) {
|
||||||
|
highestGroup = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no preferred group or word is valid -> do nothing
|
||||||
|
if (highestGroup == -1) return;
|
||||||
|
final DictionaryGroup dictionaryGroup = mDictionaryGroups.get(highestGroup);
|
||||||
|
if (validWordForDictionary == null
|
||||||
|
? isValidWord(suggestion, ALL_DICTIONARY_TYPES, dictionaryGroup)
|
||||||
|
: validWordForDictionary[highestGroup]
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
final ExpandableBinaryDictionary userDict = dictionaryGroup.getSubDict(Dictionary.TYPE_USER);
|
||||||
|
final Dictionary userHistoryDict = dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY);
|
||||||
// user history always reports words as invalid, so here we need to check isInDictionary instead
|
// user history always reports words as invalid, so here we need to check isInDictionary instead
|
||||||
// also maybe a problem: words added to dictionaries (user and history) are apparently found
|
// also maybe a problem: words added to dictionaries (user and history) are apparently found
|
||||||
// only after some delay. but this is not too bad, it just delays adding
|
// only after some delay. but this is not too bad, it just delays adding
|
||||||
|
|
||||||
final DictionaryGroup dictionaryGroup = getCurrentlyPreferredDictionaryGroup();
|
|
||||||
final ExpandableBinaryDictionary userDict = dictionaryGroup.getSubDict(Dictionary.TYPE_USER);
|
|
||||||
final Dictionary userHistoryDict = dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY);
|
|
||||||
if (userDict != null && userHistoryDict.isInDictionary(suggestion)) {
|
if (userDict != null && userHistoryDict.isInDictionary(suggestion)) {
|
||||||
if (validMainWord == null)
|
if (userDict.isInDictionary(suggestion)) // is this check necessary?
|
||||||
validMainWord = isValidWord(suggestion, ALL_DICTIONARY_TYPES, mDictionaryGroup);
|
|
||||||
if (validMainWord)
|
|
||||||
return;
|
|
||||||
if (mSecondaryDictionaryGroup != null) {
|
|
||||||
if (validSecondaryWord == null)
|
|
||||||
validSecondaryWord = isValidWord(suggestion, ALL_DICTIONARY_TYPES, mSecondaryDictionaryGroup);
|
|
||||||
if (validSecondaryWord)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (userDict.isInDictionary(suggestion))
|
|
||||||
return;
|
return;
|
||||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
|
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -813,13 +779,16 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
isValid, timeStampInSeconds);
|
isValid, timeStampInSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** returns the dictionaryGroup with most confidence, main group when tied */
|
/** returns the dictionaryGroup with most confidence, first group when tied */
|
||||||
private DictionaryGroup getCurrentlyPreferredDictionaryGroup() {
|
private DictionaryGroup getCurrentlyPreferredDictionaryGroup() {
|
||||||
final DictionaryGroup dictGroup;
|
DictionaryGroup dictGroup = null;
|
||||||
if (mSecondaryDictionaryGroup == null || mSecondaryDictionaryGroup.mConfidence <= mDictionaryGroup.mConfidence)
|
int highestConfidence = -1;
|
||||||
dictGroup = mDictionaryGroup;
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
else
|
if (dictionaryGroup.mConfidence > highestConfidence) {
|
||||||
dictGroup = mSecondaryDictionaryGroup;
|
dictGroup = dictionaryGroup;
|
||||||
|
highestConfidence = dictGroup.mConfidence;
|
||||||
|
}
|
||||||
|
}
|
||||||
return dictGroup;
|
return dictGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,39 +826,47 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
final float[] weightOfLangModelVsSpatialModel =
|
final float[] weightOfLangModelVsSpatialModel =
|
||||||
new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
|
new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
|
||||||
|
|
||||||
// start getting suggestions for secondary locale first, but in separate thread
|
// start getting suggestions for non-main locales first, but in background
|
||||||
final ArrayList<SuggestedWordInfo> dictionarySuggestionsSecondary = new ArrayList<>();
|
final ArrayList<SuggestedWordInfo>[] otherDictionarySuggestions = new ArrayList[mDictionaryGroups.size() - 1];
|
||||||
final CountDownLatch waitForSecondaryDictionary = new CountDownLatch(1);
|
final CountDownLatch waitForOtherDictionaries;
|
||||||
if (mSecondaryDictionaryGroup != null) {
|
if (mDictionaryGroups.size() > 1) {
|
||||||
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
|
waitForOtherDictionaries = new CountDownLatch(mDictionaryGroups.size() - 1);
|
||||||
@Override
|
for (int i = 1; i < mDictionaryGroups.size(); i ++) {
|
||||||
public void run() {
|
final DictionaryGroup dictionaryGroup = mDictionaryGroups.get(i);
|
||||||
dictionarySuggestionsSecondary.addAll(getSuggestions(composedData,
|
final int index = i - 1;
|
||||||
ngramContext, settingsValuesForSuggestion, sessionId, proximityInfoHandle,
|
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
|
||||||
weightOfLangModelVsSpatialModel, mSecondaryDictionaryGroup));
|
@Override
|
||||||
waitForSecondaryDictionary.countDown();
|
public void run() {
|
||||||
}
|
otherDictionarySuggestions[index] = getSuggestions(composedData,
|
||||||
});
|
ngramContext, settingsValuesForSuggestion, sessionId, proximityInfoHandle,
|
||||||
}
|
weightOfLangModelVsSpatialModel, dictionaryGroup);
|
||||||
|
waitForOtherDictionaries.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
waitForOtherDictionaries = null;
|
||||||
|
|
||||||
// get main locale suggestions
|
// get main locale suggestions
|
||||||
final ArrayList<SuggestedWordInfo> dictionarySuggestions = getSuggestions(composedData,
|
final ArrayList<SuggestedWordInfo> dictionarySuggestions = getSuggestions(composedData,
|
||||||
ngramContext, settingsValuesForSuggestion, sessionId, proximityInfoHandle,
|
ngramContext, settingsValuesForSuggestion, sessionId, proximityInfoHandle,
|
||||||
weightOfLangModelVsSpatialModel, mDictionaryGroup);
|
weightOfLangModelVsSpatialModel, mDictionaryGroups.get(0));
|
||||||
suggestionResults.addAll(dictionarySuggestions);
|
suggestionResults.addAll(dictionarySuggestions);
|
||||||
if (null != suggestionResults.mRawSuggestions) {
|
if (null != suggestionResults.mRawSuggestions) {
|
||||||
suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
|
suggestionResults.mRawSuggestions.addAll(dictionarySuggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for secondary locale suggestions
|
// wait for other locale suggestions
|
||||||
if (mSecondaryDictionaryGroup != null) {
|
if (waitForOtherDictionaries != null) {
|
||||||
try { waitForSecondaryDictionary.await(); }
|
try { waitForOtherDictionaries.await(); }
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
Log.w(TAG, "Interrupted while trying to get secondary locale suggestions", e);
|
Log.w(TAG, "Interrupted while trying to get secondary locale suggestions", e);
|
||||||
}
|
}
|
||||||
suggestionResults.addAll(dictionarySuggestionsSecondary);
|
for (int i = 1; i < mDictionaryGroups.size(); i ++) {
|
||||||
if (null != suggestionResults.mRawSuggestions) {
|
suggestionResults.addAll(otherDictionarySuggestions[i - 1]);
|
||||||
suggestionResults.mRawSuggestions.addAll(dictionarySuggestionsSecondary);
|
if (null != suggestionResults.mRawSuggestions) {
|
||||||
|
suggestionResults.mRawSuggestions.addAll(otherDictionarySuggestions[i - 1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -938,13 +915,16 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
return cachedValue;
|
return cachedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
return isValidWord(word, ALL_DICTIONARY_TYPES, mDictionaryGroup) ||
|
if (isValidWord(word, ALL_DICTIONARY_TYPES, dictionaryGroup))
|
||||||
(mSecondaryDictionaryGroup != null && isValidWord(word, ALL_DICTIONARY_TYPES, mSecondaryDictionaryGroup));
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is unused, so leave it for now (redirecting to isValidWord seems to defeat the purpose...)
|
||||||
public boolean isValidSuggestionWord(final String word) {
|
public boolean isValidSuggestionWord(final String word) {
|
||||||
return isValidWord(word, ALL_DICTIONARY_TYPES, mDictionaryGroup);
|
return isValidWord(word, ALL_DICTIONARY_TYPES, mDictionaryGroups.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidWord(final String word, final String[] dictionariesToCheck, final DictionaryGroup dictionaryGroup) {
|
private boolean isValidWord(final String word, final String[] dictionariesToCheck, final DictionaryGroup dictionaryGroup) {
|
||||||
|
@ -969,18 +949,18 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBlacklisted(final String word) {
|
private boolean isBlacklisted(final String word) {
|
||||||
if (mDictionaryGroup.blacklist.contains(word))
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
return true;
|
if (dictionaryGroup.blacklist.contains(word))
|
||||||
if (mSecondaryDictionaryGroup != null && mSecondaryDictionaryGroup.blacklist.contains(word))
|
return true;
|
||||||
return true;
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeWord(String word) {
|
public void removeWord(String word) {
|
||||||
removeWordFromGroup(word, mDictionaryGroup);
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
if (mSecondaryDictionaryGroup != null)
|
removeWordFromGroup(word, dictionaryGroup);
|
||||||
removeWordFromGroup(word, mSecondaryDictionaryGroup);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeWordFromGroup(String word, DictionaryGroup group) {
|
private void removeWordFromGroup(String word, DictionaryGroup group) {
|
||||||
|
@ -1071,16 +1051,12 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean clearSubDictionary(final String dictName) {
|
private boolean clearSubDictionary(final String dictName) {
|
||||||
final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictName);
|
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
|
||||||
if (dictionary == null) {
|
final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictName);
|
||||||
return false;
|
if (dictionary == null) {
|
||||||
}
|
return false; // should only ever happen for primary dictionary, so this is safe
|
||||||
dictionary.clear();
|
}
|
||||||
// called when not using personalized dictionaries, so should also reset secondary user history
|
dictionary.clear();
|
||||||
if (mSecondaryDictionaryGroup != null) {
|
|
||||||
final ExpandableBinaryDictionary secondaryDictionary = mSecondaryDictionaryGroup.getSubDict(dictName);
|
|
||||||
if (secondaryDictionary != null)
|
|
||||||
secondaryDictionary.clear();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1092,7 +1068,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dumpDictionaryForDebug(final String dictName) {
|
public void dumpDictionaryForDebug(final String dictName) {
|
||||||
final ExpandableBinaryDictionary dictToDump = mDictionaryGroup.getSubDict(dictName);
|
final ExpandableBinaryDictionary dictToDump = mDictionaryGroups.get(0).getSubDict(dictName);
|
||||||
if (dictToDump == null) {
|
if (dictToDump == null) {
|
||||||
Log.e(TAG, "Cannot dump " + dictName + ". "
|
Log.e(TAG, "Cannot dump " + dictName + ". "
|
||||||
+ "The dictionary is not being used for suggestion or cannot be dumped.");
|
+ "The dictionary is not being used for suggestion or cannot be dumped.");
|
||||||
|
@ -1102,10 +1078,11 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
// this is unused, so leave it for now
|
||||||
@Nonnull public List<DictionaryStats> getDictionaryStats(final Context context) {
|
@Nonnull public List<DictionaryStats> getDictionaryStats(final Context context) {
|
||||||
final ArrayList<DictionaryStats> statsOfEnabledSubDicts = new ArrayList<>();
|
final ArrayList<DictionaryStats> statsOfEnabledSubDicts = new ArrayList<>();
|
||||||
for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
|
for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
|
||||||
final ExpandableBinaryDictionary dictionary = mDictionaryGroup.getSubDict(dictType);
|
final ExpandableBinaryDictionary dictionary = mDictionaryGroups.get(0).getSubDict(dictType);
|
||||||
if (dictionary == null) continue;
|
if (dictionary == null) continue;
|
||||||
statsOfEnabledSubDicts.add(dictionary.getDictionaryStats());
|
statsOfEnabledSubDicts.add(dictionary.getDictionaryStats());
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ public final class SecondaryLocaleSettingsFragment extends SubScreenFragment {
|
||||||
List<InputMethodSubtype> subtypes = mRichImm.getMyEnabledInputMethodSubtypeList(true);
|
List<InputMethodSubtype> subtypes = mRichImm.getMyEnabledInputMethodSubtypeList(true);
|
||||||
|
|
||||||
for (InputMethodSubtype subtype : subtypes) {
|
for (InputMethodSubtype subtype : subtypes) {
|
||||||
final Locale secondaryLocale = Settings.getSecondaryLocale(getSharedPreferences(), subtype.getLocale());
|
final List<Locale> secondaryLocales = Settings.getSecondaryLocales(getSharedPreferences(), subtype.getLocale());
|
||||||
|
final Locale secondaryLocale = secondaryLocales.size() > 0 ? secondaryLocales.get(0) : null;
|
||||||
final Preference pref = new Preference(context);
|
final Preference pref = new Preference(context);
|
||||||
pref.setTitle(subtype.getDisplayName(context, BuildConfig.APPLICATION_ID, context.getApplicationInfo()));
|
pref.setTitle(subtype.getDisplayName(context, BuildConfig.APPLICATION_ID, context.getApplicationInfo()));
|
||||||
if (secondaryLocale != null)
|
if (secondaryLocale != null)
|
||||||
|
@ -94,7 +95,8 @@ public final class SecondaryLocaleSettingsFragment extends SubScreenFragment {
|
||||||
titles[i] = loc.getDisplayName(displayLocale);
|
titles[i] = loc.getDisplayName(displayLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
Locale currentSecondaryLocale = Settings.getSecondaryLocale(getSharedPreferences(), mainLocale);
|
final List<Locale> secondaryLocales = Settings.getSecondaryLocales(getSharedPreferences(), mainLocale);
|
||||||
|
final Locale currentSecondaryLocale = secondaryLocales.size() > 0 ? secondaryLocales.get(0) : null;
|
||||||
int checkedItem;
|
int checkedItem;
|
||||||
if (currentSecondaryLocale == null)
|
if (currentSecondaryLocale == null)
|
||||||
checkedItem = 0;
|
checkedItem = 0;
|
||||||
|
|
|
@ -22,14 +22,11 @@ import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import org.dslul.openboard.inputmethod.keyboard.KeyboardTheme;
|
import org.dslul.openboard.inputmethod.keyboard.KeyboardTheme;
|
||||||
import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager;
|
import org.dslul.openboard.inputmethod.latin.AudioAndHapticFeedbackManager;
|
||||||
import org.dslul.openboard.inputmethod.latin.InputAttributes;
|
import org.dslul.openboard.inputmethod.latin.InputAttributes;
|
||||||
|
@ -44,8 +41,10 @@ import org.dslul.openboard.inputmethod.latin.utils.ResourceUtils;
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.RunInLocale;
|
import org.dslul.openboard.inputmethod.latin.utils.RunInLocale;
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.StatsUtils;
|
import org.dslul.openboard.inputmethod.latin.utils.StatsUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
@ -537,14 +536,15 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
||||||
return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID, defValue);
|
return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID, defValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Locale getSecondaryLocale(final SharedPreferences prefs, final String mainLocaleString) {
|
// todo: adjust for multiple secondary locales
|
||||||
|
public static List<Locale> getSecondaryLocales(final SharedPreferences prefs, final String mainLocaleString) {
|
||||||
final Set<String> encodedLocales = prefs.getStringSet(PREF_SECONDARY_LOCALES, new HashSet<>());
|
final Set<String> encodedLocales = prefs.getStringSet(PREF_SECONDARY_LOCALES, new HashSet<>());
|
||||||
for (String loc : encodedLocales) {
|
for (String loc : encodedLocales) {
|
||||||
String[] locales = loc.split("§");
|
String[] locales = loc.split("§");
|
||||||
if (locales.length == 2 && locales[0].equals(mainLocaleString.toLowerCase(Locale.ENGLISH)))
|
if (locales.length == 2 && locales[0].equals(mainLocaleString.toLowerCase(Locale.ENGLISH)))
|
||||||
return LocaleUtils.constructLocaleFromString(locales[1]);
|
return new ArrayList<Locale>() {{ add(LocaleUtils.constructLocaleFromString(locales[1])); }};
|
||||||
}
|
}
|
||||||
return null;
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Colors getColors(final Context context, final SharedPreferences prefs) {
|
public static Colors getColors(final Context context, final SharedPreferences prefs) {
|
||||||
|
|
|
@ -21,13 +21,9 @@ import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.ColorFilter;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
|
||||||
import androidx.core.graphics.BlendModeColorFilterCompat;
|
|
||||||
import androidx.core.graphics.BlendModeCompat;
|
|
||||||
|
|
||||||
import org.dslul.openboard.inputmethod.compat.AppWorkaroundsUtils;
|
import org.dslul.openboard.inputmethod.compat.AppWorkaroundsUtils;
|
||||||
import org.dslul.openboard.inputmethod.latin.InputAttributes;
|
import org.dslul.openboard.inputmethod.latin.InputAttributes;
|
||||||
import org.dslul.openboard.inputmethod.latin.R;
|
import org.dslul.openboard.inputmethod.latin.R;
|
||||||
|
@ -40,6 +36,7 @@ import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils;
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.TargetPackageInfoGetterTask;
|
import org.dslul.openboard.inputmethod.latin.utils.TargetPackageInfoGetterTask;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
@ -91,7 +88,7 @@ public class SettingsValues {
|
||||||
public final long mClipboardHistoryRetentionTime;
|
public final long mClipboardHistoryRetentionTime;
|
||||||
public final boolean mOneHandedModeEnabled;
|
public final boolean mOneHandedModeEnabled;
|
||||||
public final int mOneHandedModeGravity;
|
public final int mOneHandedModeGravity;
|
||||||
public final Locale mSecondaryLocale;
|
public final List<Locale> mSecondaryLocales;
|
||||||
// Use bigrams to predict the next word when there is no input for it yet
|
// Use bigrams to predict the next word when there is no input for it yet
|
||||||
public final boolean mBigramPredictionEnabled;
|
public final boolean mBigramPredictionEnabled;
|
||||||
public final boolean mGestureInputEnabled;
|
public final boolean mGestureInputEnabled;
|
||||||
|
@ -254,7 +251,7 @@ public class SettingsValues {
|
||||||
mClipboardHistoryRetentionTime = Settings.readClipboardHistoryRetentionTime(prefs, res);
|
mClipboardHistoryRetentionTime = Settings.readClipboardHistoryRetentionTime(prefs, res);
|
||||||
mOneHandedModeEnabled = Settings.readOneHandedModeEnabled(prefs);
|
mOneHandedModeEnabled = Settings.readOneHandedModeEnabled(prefs);
|
||||||
mOneHandedModeGravity = Settings.readOneHandedModeGravity(prefs);
|
mOneHandedModeGravity = Settings.readOneHandedModeGravity(prefs);
|
||||||
mSecondaryLocale = Settings.getSecondaryLocale(prefs, RichInputMethodManager.getInstance().getCurrentSubtypeLocale().toString());
|
mSecondaryLocales = Settings.getSecondaryLocales(prefs, RichInputMethodManager.getInstance().getCurrentSubtypeLocale().toString());
|
||||||
|
|
||||||
mColors = Settings.getColors(context, prefs);
|
mColors = Settings.getColors(context, prefs);
|
||||||
mColors.createColorFilters(prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false));
|
mColors.createColorFilters(prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue