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:
Helium314 2023-08-11 00:58:33 +02:00
parent e7e05ca1c2
commit 592f4aab0c
5 changed files with 297 additions and 294 deletions

View file

@ -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
&& !secondaryLocale.getLanguage().equals(subtype.getLocale().getLanguage()) final List<Locale> secondaryLocalesToUse = withoutDuplicateLanguages(secondaryLocales, 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) {

View file

@ -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 (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
for (final String dictType : ALL_DICTIONARY_TYPES) { for (final String dictType : ALL_DICTIONARY_TYPES) {
Dictionary dict = mDictionaryGroup.getDict(dictType); Dictionary dict = dictionaryGroup.getDict(dictType);
if (dict != null) dict.onFinishInput(); if (dict != null) dict.onFinishInput();
} }
if (mSecondaryDictionaryGroup != null)
for (final String dictType : ALL_DICTIONARY_TYPES) {
Dictionary dict = mSecondaryDictionaryGroup.getDict(dictType);
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.
for (DictionaryGroup dictionaryGroup : mDictionaryGroups) {
final ArrayList<String> dictTypeForLocale = new ArrayList<>(); final ArrayList<String> dictTypeForLocale = new ArrayList<>();
existingDictionariesToCleanup.put(newLocale, dictTypeForLocale); existingDictionariesToCleanup.put(dictionaryGroup.mLocale, dictTypeForLocale);
final DictionaryGroup currentDictionaryGroupForLocale =
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());
for (Locale locale : allLocales) {
// get existing dictionary group for new locale
final DictionaryGroup oldDictionaryGroupForLocale =
findDictionaryGroupWithLocale(mDictionaryGroups, locale);
final ArrayList<String> dictTypesToCleanupForLocale = final ArrayList<String> dictTypesToCleanupForLocale =
existingDictionariesToCleanup.get(newLocale); existingDictionariesToCleanup.get(locale);
final boolean noExistingDictsForThisLocale = (null == dictionaryGroupForLocale); final boolean noExistingDictsForThisLocale = (null == oldDictionaryGroupForLocale);
// create new or re-use already loaded main dict
final Dictionary mainDict; final Dictionary mainDict;
if (forceReloadMainDictionary || noExistingDictsForThisLocale if (forceReloadMainDictionary || noExistingDictsForThisLocale
|| !dictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) { || !oldDictionaryGroupForLocale.hasDict(Dictionary.TYPE_MAIN, account)) {
mainDict = null; mainDict = null;
} else { } else {
mainDict = dictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN); mainDict = oldDictionaryGroupForLocale.getDict(Dictionary.TYPE_MAIN);
dictTypesToCleanupForLocale.remove(Dictionary.TYPE_MAIN); dictTypesToCleanupForLocale.remove(Dictionary.TYPE_MAIN);
} }
// create new or re-use already loaded sub-dicts
final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>(); final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
for (final String subDictType : subDictTypesToUse) { for (final String subDictType : subDictTypesToUse) {
final ExpandableBinaryDictionary subDict; final ExpandableBinaryDictionary subDict;
if (noExistingDictsForThisLocale if (noExistingDictsForThisLocale
|| !dictionaryGroupForLocale.hasDict(subDictType, account)) { || !oldDictionaryGroupForLocale.hasDict(subDictType, account)) {
// Create a new dictionary. // Create a new dictionary.
subDict = getSubDict(subDictType, context, newLocale, null /* dictFile */, subDict = getSubDict(subDictType, context, locale, null /* dictFile */, dictNamePrefix, account);
dictNamePrefix, account);
} else { } else {
// Reuse the existing dictionary, and don't close it at the end // Reuse the existing dictionary, and don't close it at the end
subDict = dictionaryGroupForLocale.getSubDict(subDictType); subDict = oldDictionaryGroupForLocale.getSubDict(subDictType);
dictTypesToCleanupForLocale.remove(subDictType); dictTypesToCleanupForLocale.remove(subDictType);
} }
subDicts.put(subDictType, subDict); subDicts.put(subDictType, subDict);
} }
DictionaryGroup newDictionaryGroup = DictionaryGroup newDictGroup = new DictionaryGroup(locale, mainDict, account, subDicts);
new DictionaryGroup(newLocale, mainDict, account, subDicts); newDictionaryGroups.add(newDictGroup);
// create / load secondary dictionary // load blacklist
final Locale secondaryLocale = Settings.getInstance().getCurrent().mSecondaryLocale; if (noExistingDictsForThisLocale) {
final DictionaryGroup newSecondaryDictionaryGroup; newDictGroup.blacklistFileName = context.getFilesDir().getAbsolutePath() + File.separator + "blacklists" + File.separator + locale.toString().toLowerCase(Locale.ENGLISH) + ".txt";
final Map<String, ExpandableBinaryDictionary> secondarySubDicts = new HashMap<>(); if (!new File(newDictGroup.blacklistFileName).exists())
new File(context.getFilesDir().getAbsolutePath() + File.separator + "blacklists").mkdirs();
if (secondaryLocale != null && newDictGroup.blacklist.addAll(readBlacklistFile(newDictGroup.blacklistFileName));
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) {
final ExpandableBinaryDictionary subDict =
getSubDict(subDictType, context, secondaryLocale, null, dictNamePrefix, account);
secondarySubDicts.put(subDictType, subDict);
dictTypesToCleanUp.remove(subDictType);
}
final Dictionary secondaryMainDict;
if (forceReloadMainDictionary || findDictionaryGroupWithLocale(mSecondaryDictionaryGroup, secondaryLocale) == null
|| !mSecondaryDictionaryGroup.hasDict(Dictionary.TYPE_MAIN, account)) {
secondaryMainDict = null;
} else { } else {
if (mSecondaryDictionaryGroup == null) // re-use if possible
secondaryMainDict = null; newDictGroup.blacklistFileName = oldDictionaryGroupForLocale.blacklistFileName;
else newDictGroup.blacklist.addAll(oldDictionaryGroupForLocale.blacklist);
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
// after user changed enabled types (e.g. disable personalized suggestions)
existingDictionariesToCleanup.put(secondaryLocale, dictTypesToCleanUp);
} else {
newSecondaryDictionaryGroup = null;
}
// 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<>();
for (int i = 0; i < locales.size(); i++) {
Locale locale = locales.get(i);
DictionaryGroup dictionaryGroup = findDictionaryGroupWithLocale(mDictionaryGroups, locale);
if (null == dictionaryGroup) { if (null == dictionaryGroup) {
// This should never happen, but better safe than crashy // This should never happen, but better safe than crashy
Log.w(TAG, "Expected a dictionary group for " + locale + " but none found"); Log.w(TAG, "Expected a dictionary group for " + locale + " but none found");
return; return;
} }
final Dictionary mainDict = dictionaryGroups.add(dictionaryGroup);
DictionaryFactory.createMainDictionaryFromManager(context, locale); // do nothing if main dict already initialized
if (dictionaryGroup.mMainDict != null && dictionaryGroup.mMainDict.isInitialized()) {
final DictionaryGroup secondaryDictionaryGroup; mainDicts[i] = null;
if (secondaryLocale == null) continue;
secondaryDictionaryGroup = null; }
else mainDicts[i] = DictionaryFactory.createMainDictionaryFromManager(context, dictionaryGroup.mLocale);
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);
if (mainDicts[i] == null)
continue;
if (locale.equals(dictionaryGroups.get(i).mLocale)) {
dictionaryGroups.get(i).setMainDict(mainDicts[i]);
} else { } else {
// Dictionary facilitator has been reset for another locale. // Dictionary facilitator has been reset for another locale.
mainDict.close(); 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 (DictionaryGroup dictionaryGroup : dictionaryGroupsToClose) {
for (final String dictType : ALL_DICTIONARY_TYPES) { for (final String dictType : ALL_DICTIONARY_TYPES) {
mainDictionaryGroupToClose.closeDict(dictType); dictionaryGroup.closeDict(dictType);
if (secondaryDictionaryGroupToClose != null) }
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 mSecondaryDictionaryGroup.decreaseConfidence();
} }
} else
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) {
waitForOtherDictionaries = new CountDownLatch(mDictionaryGroups.size() - 1);
for (int i = 1; i < mDictionaryGroups.size(); i ++) {
final DictionaryGroup dictionaryGroup = mDictionaryGroups.get(i);
final int index = i - 1;
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() { ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute(new Runnable() {
@Override @Override
public void run() { public void run() {
dictionarySuggestionsSecondary.addAll(getSuggestions(composedData, otherDictionarySuggestions[index] = getSuggestions(composedData,
ngramContext, settingsValuesForSuggestion, sessionId, proximityInfoHandle, ngramContext, settingsValuesForSuggestion, sessionId, proximityInfoHandle,
weightOfLangModelVsSpatialModel, mSecondaryDictionaryGroup)); weightOfLangModelVsSpatialModel, dictionaryGroup);
waitForSecondaryDictionary.countDown(); 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 ++) {
suggestionResults.addAll(otherDictionarySuggestions[i - 1]);
if (null != suggestionResults.mRawSuggestions) { if (null != suggestionResults.mRawSuggestions) {
suggestionResults.mRawSuggestions.addAll(dictionarySuggestionsSecondary); 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) {
final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictName);
if (dictionary == null) { if (dictionary == null) {
return false; return false; // should only ever happen for primary dictionary, so this is safe
} }
dictionary.clear(); dictionary.clear();
// called when not using personalized dictionaries, so should also reset secondary user history
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());
} }

View file

@ -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;

View file

@ -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) {

View file

@ -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));