mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-18 16:03:12 +00:00
Switch to new language settings (#89)
* add language settings * move to settings instead of language selection at end of setup wizard * allow storing enabled subtypes in preferences * make language selection and input method picker work with new system * deal with weird issue of getSystemLocales returning inconsistent locales * add details text to language settings * make usused settings inaccessible * better deal with "zz" subtypes, move hungarian (qwerty) from method.xml so a separate aditional subtype * scrape some strings+translations from android system + latinime * rename strings, add comment for unused string
This commit is contained in:
parent
e0c054ce09
commit
f32395366d
129 changed files with 1816 additions and 207 deletions
|
@ -24,6 +24,7 @@ import android.view.ContextThemeWrapper;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
@ -593,4 +594,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
|
|||
}
|
||||
return mKeyboardLayoutSet.getScriptId();
|
||||
}
|
||||
|
||||
public void switchToSubtype(InputMethodSubtype subtype) {
|
||||
Log.i("test1", "switch to "+subtype.getLocale());
|
||||
mLatinIME.switchToSubtype(subtype);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ import android.view.Window;
|
|||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import org.dslul.openboard.inputmethod.accessibility.AccessibilityUtils;
|
||||
|
@ -75,6 +76,7 @@ import org.dslul.openboard.inputmethod.latin.permissions.PermissionsManager;
|
|||
import org.dslul.openboard.inputmethod.latin.personalization.PersonalizationHelper;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.Settings;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.SettingsActivity;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.SubtypeSettingsKt;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.SettingsValues;
|
||||
import org.dslul.openboard.inputmethod.latin.suggestions.SuggestionStripView;
|
||||
import org.dslul.openboard.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
|
||||
|
@ -82,6 +84,7 @@ import org.dslul.openboard.inputmethod.latin.touchinputconsumer.GestureConsumer;
|
|||
import org.dslul.openboard.inputmethod.latin.utils.ApplicationUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DialogUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.InputMethodPickerKt;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.IntentUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.JniUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.LeakGuardHandlerWrapper;
|
||||
|
@ -578,7 +581,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
}
|
||||
}
|
||||
|
||||
static final class SubtypeState {
|
||||
final class SubtypeState {
|
||||
private InputMethodSubtype mLastActiveSubtype;
|
||||
private boolean mCurrentSubtypeHasBeenUsed;
|
||||
|
||||
|
@ -586,9 +589,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
mCurrentSubtypeHasBeenUsed = true;
|
||||
}
|
||||
|
||||
public void switchSubtype(final IBinder token, final RichInputMethodManager richImm) {
|
||||
final InputMethodSubtype currentSubtype = richImm.getInputMethodManager()
|
||||
.getCurrentInputMethodSubtype();
|
||||
public void switchSubtype(final RichInputMethodManager richImm) {
|
||||
final InputMethodSubtype currentSubtype = richImm.getCurrentSubtype().getRawSubtype();
|
||||
final InputMethodSubtype lastActiveSubtype = mLastActiveSubtype;
|
||||
final boolean currentSubtypeHasBeenUsed = mCurrentSubtypeHasBeenUsed;
|
||||
if (currentSubtypeHasBeenUsed) {
|
||||
|
@ -598,10 +600,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
if (currentSubtypeHasBeenUsed
|
||||
&& richImm.checkIfSubtypeBelongsToThisImeAndEnabled(lastActiveSubtype)
|
||||
&& !currentSubtype.equals(lastActiveSubtype)) {
|
||||
richImm.setInputMethodAndSubtype(token, lastActiveSubtype);
|
||||
switchToSubtype(lastActiveSubtype);
|
||||
return;
|
||||
}
|
||||
richImm.switchToNextInputMethod(token, true /* onlyCurrentIme */);
|
||||
// switchSubtype is called only for internal switching, so let's just switch to the next subtype
|
||||
switchToSubtype(richImm.getNextSubtypeInThisIme(true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -624,6 +627,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
public void onCreate() {
|
||||
Settings.init(this);
|
||||
DebugFlags.init(DeviceProtectedUtils.getSharedPreferences(this));
|
||||
SubtypeSettingsKt.init(this);
|
||||
RichInputMethodManager.init(this);
|
||||
mRichImm = RichInputMethodManager.getInstance();
|
||||
AudioAndHapticFeedbackManager.init(this);
|
||||
|
@ -802,6 +806,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
@Override
|
||||
public void onConfigurationChanged(final Configuration conf) {
|
||||
SettingsValues settingsValues = mSettings.getCurrent();
|
||||
SubtypeSettingsKt.reloadSystemLocales(this);
|
||||
if (settingsValues.mDisplayOrientation != conf.orientation) {
|
||||
mHandler.startOrientationChanging();
|
||||
mInputLogic.onOrientationChange(mSettings.getCurrent());
|
||||
|
@ -918,6 +923,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
loadKeyboard();
|
||||
}
|
||||
|
||||
/** alias to onCurrentInputMethodSubtypeChanged with a better name, as it's also used for internal switching */
|
||||
public void switchToSubtype(final InputMethodSubtype subtype) {
|
||||
onCurrentInputMethodSubtypeChanged(subtype);
|
||||
}
|
||||
|
||||
void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
|
||||
super.onStartInput(editorInfo, restarting);
|
||||
|
||||
|
@ -1425,8 +1435,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
switch (requestCode) {
|
||||
case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER:
|
||||
if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
|
||||
mRichImm.getInputMethodManager().showInputMethodPicker();
|
||||
return true;
|
||||
InputMethodPickerKt.showInputMethodPicker(this, mRichImm, mKeyboardSwitcher.getMainKeyboardView().getWindowToken());
|
||||
return true; // todo: don't show and return if dialog already shown? but how can this happen?
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1478,19 +1488,42 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
return mOptionsDialog != null && mOptionsDialog.isShowing();
|
||||
}
|
||||
|
||||
// todo: remove, this is really not necessary
|
||||
public void switchLanguage(final InputMethodSubtype subtype) {
|
||||
final IBinder token = getWindow().getWindow().getAttributes().token;
|
||||
mRichImm.setInputMethodAndSubtype(token, subtype);
|
||||
switchToSubtype(subtype);
|
||||
}
|
||||
|
||||
// TODO: Revise the language switch key behavior to make it much smarter and more reasonable.
|
||||
public void switchToNextSubtype() {
|
||||
final IBinder token = getWindow().getWindow().getAttributes().token;
|
||||
if (shouldSwitchToOtherInputMethods()) {
|
||||
mRichImm.switchToNextInputMethod(token, true /* onlyCurrentIme */);
|
||||
// todo: this is the old behavior, is this actually wanted?
|
||||
// maybe make the language switch key more configurable
|
||||
boolean moreThanOneSubtype = mRichImm.getMyEnabledInputMethodSubtypeList(false).size() > 1;
|
||||
final InputMethodSubtype nextSubtype = mRichImm.getNextSubtypeInThisIme(moreThanOneSubtype);
|
||||
if (nextSubtype != null) {
|
||||
switchToSubtype(nextSubtype);
|
||||
} else {
|
||||
// we are at end of the internal subtype list, switch to next input method
|
||||
// (for now) don't care about which input method and subtype exactly, let the system choose
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
switchToNextInputMethod(false);
|
||||
} else {
|
||||
final IBinder token = getWindow().getWindow().getAttributes().token;
|
||||
mRichImm.getInputMethodManager().switchToNextInputMethod(token, false);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
mSubtypeState.switchSubtype(token, mRichImm);
|
||||
mSubtypeState.switchSubtype(mRichImm);
|
||||
}
|
||||
|
||||
public void switchInputMethodAndSubtype(final InputMethodInfo imi, final InputMethodSubtype subtype) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
switchInputMethod(imi.getId(), subtype);
|
||||
} else {
|
||||
final IBinder token = getWindow().getWindow().getAttributes().token;
|
||||
mRichImm.getInputMethodManager().setInputMethodAndSubtype(token, imi.getId(), subtype);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
|
||||
|
@ -2038,15 +2071,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
}
|
||||
|
||||
public boolean shouldSwitchToOtherInputMethods() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||
return shouldOfferSwitchingToNextInputMethod();
|
||||
// TODO: Revisit here to reorganize the settings. Probably we can/should use different
|
||||
// strategy once the implementation of
|
||||
// {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well.
|
||||
final boolean fallbackValue = mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList;
|
||||
final IBinder token = getWindow().getWindow().getAttributes().token;
|
||||
if (token == null) {
|
||||
return fallbackValue;
|
||||
return mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList;
|
||||
}
|
||||
return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue);
|
||||
return mRichImm.getInputMethodManager().shouldOfferSwitchingToNextInputMethod(token);
|
||||
}
|
||||
|
||||
public boolean shouldShowLanguageSwitchKey() {
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.dslul.openboard.inputmethod.annotations.UsedForTesting;
|
|||
import org.dslul.openboard.inputmethod.compat.InputMethodManagerCompatWrapper;
|
||||
import org.dslul.openboard.inputmethod.compat.InputMethodSubtypeCompatUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.Settings;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.SubtypeSettingsKt;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.AdditionalSubtypeUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.LanguageOnSpacebarUtils;
|
||||
|
@ -100,17 +101,16 @@ public class RichInputMethodManager {
|
|||
mInputMethodInfoCache = new InputMethodInfoCache(
|
||||
mImmWrapper.mImm, context.getPackageName());
|
||||
|
||||
// Initialize additional subtypes.
|
||||
// Initialize subtype utils.
|
||||
SubtypeLocaleUtils.init(context);
|
||||
final InputMethodSubtype[] additionalSubtypes = getAdditionalSubtypes();
|
||||
mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
|
||||
getInputMethodIdOfThisIme(), additionalSubtypes);
|
||||
|
||||
// Initialize the current input method subtype and the shortcut IME.
|
||||
refreshSubtypeCaches();
|
||||
}
|
||||
|
||||
public InputMethodSubtype[] getAdditionalSubtypes() {
|
||||
public InputMethodSubtype[] getAdditionalSubtypes() { // todo: can be removed
|
||||
// todo: this should read the enabled subtypes setting
|
||||
// either use or remove the default additional subtypes
|
||||
final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(mContext);
|
||||
final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
|
||||
prefs, mContext.getResources());
|
||||
|
@ -129,7 +129,8 @@ public class RichInputMethodManager {
|
|||
}
|
||||
|
||||
public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
|
||||
if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) {
|
||||
// todo: don't want this any more, and actually mImmWrapper.switchToNextInputMethod can be removed
|
||||
if (false && mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) {
|
||||
return true;
|
||||
}
|
||||
// Was not able to call {@link InputMethodManager#switchToNextInputMethodIBinder,boolean)}
|
||||
|
@ -140,10 +141,27 @@ public class RichInputMethodManager {
|
|||
return switchToNextInputMethodAndSubtype(token);
|
||||
}
|
||||
|
||||
public @Nullable InputMethodSubtype getNextSubtypeInThisIme(final boolean onlyCurrentIme) {
|
||||
final InputMethodSubtype currentSubtype = getCurrentSubtype().getRawSubtype();
|
||||
final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList(true);
|
||||
final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
|
||||
if (currentIndex == INDEX_NOT_FOUND) {
|
||||
Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype="
|
||||
+ SubtypeLocaleUtils.getSubtypeNameForLogging(currentSubtype));
|
||||
if (onlyCurrentIme) return enabledSubtypes.get(0); // just return first enabled subtype
|
||||
else return null;
|
||||
}
|
||||
final int nextIndex = (currentIndex + 1) % enabledSubtypes.size();
|
||||
if (nextIndex <= currentIndex && !onlyCurrentIme) {
|
||||
// The current subtype is the last or only enabled one and it needs to switch to next IME.
|
||||
return null;
|
||||
}
|
||||
return enabledSubtypes.get(nextIndex);
|
||||
}
|
||||
|
||||
private boolean switchToNextInputSubtypeInThisIme(final IBinder token,
|
||||
final boolean onlyCurrentIme) {
|
||||
final InputMethodManager imm = mImmWrapper.mImm;
|
||||
final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
|
||||
final InputMethodSubtype currentSubtype = getCurrentSubtype().getRawSubtype();
|
||||
final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList(
|
||||
true /* allowsImplicitlySelectedSubtypes */);
|
||||
final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
|
||||
|
@ -159,7 +177,7 @@ public class RichInputMethodManager {
|
|||
return false;
|
||||
}
|
||||
final InputMethodSubtype nextSubtype = enabledSubtypes.get(nextIndex);
|
||||
setInputMethodAndSubtype(token, nextSubtype);
|
||||
setInputMethodAndSubtype(token, nextSubtype); // todo: not working any more, but switchToNextInputSubtypeInThisIme isn't called anyway...
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -266,8 +284,14 @@ public class RichInputMethodManager {
|
|||
if (cachedList != null) {
|
||||
return cachedList;
|
||||
}
|
||||
final List<InputMethodSubtype> result = mImm.getEnabledInputMethodSubtypeList(
|
||||
imi, allowsImplicitlySelectedSubtypes);
|
||||
final List<InputMethodSubtype> result;
|
||||
if (imi == getInputMethodOfThisIme()) {
|
||||
// allowsImplicitlySelectedSubtypes means system should choose if nothing is enabled,
|
||||
// use it to fall back to system locales or en_US to avoid returning an empty list
|
||||
result = SubtypeSettingsKt.getEnabledSubtypes(DeviceProtectedUtils.getSharedPreferences(sInstance.mContext), allowsImplicitlySelectedSubtypes);
|
||||
} else {
|
||||
result = mImm.getEnabledInputMethodSubtypeList(imi, allowsImplicitlySelectedSubtypes);
|
||||
}
|
||||
cache.put(imi, result);
|
||||
return result;
|
||||
}
|
||||
|
@ -309,6 +333,7 @@ public class RichInputMethodManager {
|
|||
|
||||
private static int getSubtypeIndexInList(final InputMethodSubtype subtype,
|
||||
final List<InputMethodSubtype> subtypes) {
|
||||
// todo: why not simply subtypes.indexOf(subtype)? should do exactly the same, even return the same value -1 if not found
|
||||
final int count = subtypes.size();
|
||||
for (int index = 0; index < count; index++) {
|
||||
final InputMethodSubtype ims = subtypes.get(index);
|
||||
|
@ -470,10 +495,13 @@ public class RichInputMethodManager {
|
|||
}
|
||||
|
||||
public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) {
|
||||
mImmWrapper.mImm.setInputMethodAndSubtype(
|
||||
token, getInputMethodIdOfThisIme(), subtype);
|
||||
// todo: mImm stuff doesn't work any more, need sth like notifySubtypeChanged to actually trigger a reload
|
||||
// try calling this instead, probably best before loading keyboard
|
||||
// essentially it should do that update thing?
|
||||
onSubtypeChanged(subtype);
|
||||
}
|
||||
|
||||
// todo: remove together with additional subtype settings
|
||||
public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
|
||||
mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
|
||||
getInputMethodIdOfThisIme(), subtypes);
|
||||
|
@ -482,7 +510,7 @@ public class RichInputMethodManager {
|
|||
refreshSubtypeCaches();
|
||||
}
|
||||
|
||||
private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi,
|
||||
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi,
|
||||
final boolean allowsImplicitlySelectedSubtypes) {
|
||||
return mInputMethodInfoCache.getEnabledInputMethodSubtypeList(
|
||||
imi, allowsImplicitlySelectedSubtypes);
|
||||
|
@ -490,10 +518,12 @@ public class RichInputMethodManager {
|
|||
|
||||
public void refreshSubtypeCaches() {
|
||||
mInputMethodInfoCache.clear();
|
||||
updateCurrentSubtype(mImmWrapper.mImm.getCurrentInputMethodSubtype());
|
||||
SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(mContext);
|
||||
updateCurrentSubtype(SubtypeSettingsKt.getSelectedSubtype(prefs));
|
||||
updateShortcutIme();
|
||||
}
|
||||
|
||||
// todo: remove
|
||||
public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder,
|
||||
boolean defaultValue) {
|
||||
// Use the default value instead on Jelly Bean MR2 and previous where
|
||||
|
@ -505,6 +535,7 @@ public class RichInputMethodManager {
|
|||
return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder);
|
||||
}
|
||||
|
||||
// todo: remove?
|
||||
public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
|
||||
final Locale systemLocale = mContext.getResources().getConfiguration().locale;
|
||||
final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
|
||||
|
@ -530,10 +561,12 @@ public class RichInputMethodManager {
|
|||
return true;
|
||||
}
|
||||
|
||||
private void updateCurrentSubtype(@Nullable final InputMethodSubtype subtype) {
|
||||
private void updateCurrentSubtype(final InputMethodSubtype subtype) {
|
||||
SubtypeSettingsKt.setSelectedSubtype(DeviceProtectedUtils.getSharedPreferences(mContext), subtype);
|
||||
mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype);
|
||||
}
|
||||
|
||||
// todo: what is shortcutIme? the voice input? if yes, rename it and other things like mHasShortcutKey
|
||||
private void updateShortcutIme() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Update shortcut IME from : "
|
||||
|
|
|
@ -26,7 +26,6 @@ import android.content.pm.PackageManager;
|
|||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.Settings;
|
||||
|
@ -66,12 +65,6 @@ public final class SystemBroadcastReceiver extends BroadcastReceiver {
|
|||
final String intentAction = intent.getAction();
|
||||
if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intentAction)) {
|
||||
Log.i(TAG, "Package has been replaced: " + context.getPackageName());
|
||||
// Need to restore additional subtypes because system always clears additional
|
||||
// subtypes when the package is replaced.
|
||||
RichInputMethodManager.init(context);
|
||||
final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
|
||||
final InputMethodSubtype[] additionalSubtypes = richImm.getAdditionalSubtypes();
|
||||
richImm.setAdditionalInputMethodSubtypes(additionalSubtypes);
|
||||
toggleAppIcon(context);
|
||||
} else if (Intent.ACTION_BOOT_COMPLETED.equals(intentAction)) {
|
||||
Log.i(TAG, "Boot has been completed");
|
||||
|
|
|
@ -101,8 +101,6 @@ class AppearanceSettingsFragment : SubScreenFragment(), Preference.OnPreferenceC
|
|||
super.onResume()
|
||||
updateThemePreferencesState()
|
||||
updateAfterPreferenceChanged()
|
||||
CustomInputStyleSettingsFragment.updateCustomInputStylesSummary(
|
||||
findPreference(Settings.PREF_CUSTOM_INPUT_STYLES))
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.io.File
|
|||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
@Suppress("deprecation")
|
||||
class DictionarySettingsFragment : SubScreenFragment() {
|
||||
|
||||
// dict for which dialog is currently open (if any)
|
||||
|
@ -348,7 +349,7 @@ class DictionarySettingsFragment : SubScreenFragment() {
|
|||
private const val DICTIONARY_REQUEST_CODE = 96834
|
||||
private const val DICTIONARY_URL =
|
||||
"https://codeberg.org/Helium314/aosp-dictionaries"
|
||||
private const val USER_DICTIONARY_SUFFIX = "user.dict"
|
||||
const val USER_DICTIONARY_SUFFIX = "user.dict"
|
||||
|
||||
private const val DICT_INTERNAL_AND_USER = 2
|
||||
private const val DICT_INTERNAL_ONLY = 1
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
package org.dslul.openboard.inputmethod.latin.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.preference.Preference
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.dslul.openboard.inputmethod.latin.R
|
||||
import org.dslul.openboard.inputmethod.latin.utils.*
|
||||
|
||||
class LanguageFilterListPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs) {
|
||||
|
||||
private var preferenceView: View? = null
|
||||
private val adapter = LanguageAdapter(emptyList(), context)
|
||||
private val sortedSubtypes = mutableListOf<MutableList<SubtypeInfo>>()
|
||||
|
||||
fun setSettingsFragment(newFragment: LanguageSettingsFragment?) {
|
||||
adapter.fragment = newFragment
|
||||
}
|
||||
|
||||
override fun onBindView(view: View?) {
|
||||
super.onBindView(view)
|
||||
preferenceView = view
|
||||
preferenceView?.findViewById<RecyclerView>(R.id.language_list)?.adapter = adapter
|
||||
val searchField = preferenceView?.findViewById<EditText>(R.id.search_field)!!
|
||||
searchField.doAfterTextChanged { text ->
|
||||
adapter.list = sortedSubtypes.filter { it.first().displayName.startsWith(text.toString(), ignoreCase = true) }
|
||||
}
|
||||
view?.doOnLayout {
|
||||
// set correct height for recycler view, so there is no scrolling of the outside view happening
|
||||
// not sure how, but probably this can be achieved in xml...
|
||||
val windowFrame = Rect()
|
||||
it.getWindowVisibleDisplayFrame(windowFrame) // rect the app has, we want the bottom (above screen bottom/navbar/keyboard)
|
||||
val globalRect = Rect()
|
||||
it.getGlobalVisibleRect(globalRect) // rect the view takes, we want the top (below the system language preference)
|
||||
val recycler = it.findViewById<RecyclerView>(R.id.language_list)
|
||||
|
||||
val newHeight = windowFrame.bottom - globalRect.top - it.findViewById<View>(R.id.search_container).height
|
||||
recycler.layoutParams = recycler.layoutParams.apply { height = newHeight }
|
||||
}
|
||||
}
|
||||
|
||||
fun setLanguages(list: Collection<MutableList<SubtypeInfo>>, disableSwitches: Boolean) {
|
||||
sortedSubtypes.clear()
|
||||
sortedSubtypes.addAll(list)
|
||||
adapter.disableSwitches = disableSwitches
|
||||
adapter.list = sortedSubtypes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LanguageAdapter(list: List<MutableList<SubtypeInfo>> = listOf(), context: Context) :
|
||||
RecyclerView.Adapter<LanguageAdapter.ViewHolder>() {
|
||||
var disableSwitches = false
|
||||
private val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||
var fragment: LanguageSettingsFragment? = null
|
||||
|
||||
var list: List<MutableList<SubtypeInfo>> = list
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.onBind(list[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = list.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LanguageAdapter.ViewHolder {
|
||||
val v = LayoutInflater.from(parent.context).inflate(R.layout.language_list_item, parent, false)
|
||||
return ViewHolder(v)
|
||||
}
|
||||
|
||||
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
fun onBind(infos: MutableList<SubtypeInfo>) {
|
||||
fun setupDetailsTextAndSwitch() {
|
||||
// this is unrelated -> rename it
|
||||
view.findViewById<TextView>(R.id.language_details).apply {
|
||||
// input styles if more than one in infos
|
||||
val sb = StringBuilder()
|
||||
if (infos.size > 1) {
|
||||
sb.append(infos.joinToString(", ") {// separator ok? because for some languages it might not be...
|
||||
SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it.subtype)
|
||||
?: SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(it.subtype)
|
||||
})
|
||||
}
|
||||
val secondaryLocales = Settings.getSecondaryLocales(prefs, infos.first().subtype.locale)
|
||||
if (secondaryLocales.isNotEmpty()) {
|
||||
if (sb.isNotEmpty())
|
||||
sb.append("\n")
|
||||
sb.append(Settings.getSecondaryLocales(prefs, infos.first().subtype.locale)
|
||||
.joinToString(", ") {
|
||||
it.getDisplayName(context.resources.configuration.locale)
|
||||
})
|
||||
}
|
||||
text = sb.toString()
|
||||
if (text.isBlank()) isGone = true
|
||||
else isVisible = true
|
||||
}
|
||||
|
||||
view.findViewById<Switch>(R.id.language_switch).apply {
|
||||
isEnabled = !disableSwitches && infos.size == 1
|
||||
// take care: isChecked changes if the language is scrolled out of view and comes back!
|
||||
// disable the change listener when setting the checked status on scroll
|
||||
// so it's only triggered on user interactions
|
||||
setOnCheckedChangeListener(null)
|
||||
isChecked = disableSwitches || infos.any { it.isEnabled }
|
||||
setOnCheckedChangeListener { _, b ->
|
||||
if (b) {
|
||||
if (infos.size == 1) {
|
||||
addEnabledSubtype(prefs, infos.first().subtype)
|
||||
infos.single().isEnabled = true
|
||||
} else {
|
||||
LanguageSettingsDialog(view.context, infos, fragment, disableSwitches, { setupDetailsTextAndSwitch() }).show()
|
||||
}
|
||||
} else {
|
||||
if (infos.size == 1) {
|
||||
removeEnabledSubtype(prefs, infos.first().subtype)
|
||||
infos.single().isEnabled = false
|
||||
} else {
|
||||
LanguageSettingsDialog(view.context, infos, fragment, disableSwitches, { setupDetailsTextAndSwitch() }).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.findViewById<TextView>(R.id.language_name).text = infos.first().displayName
|
||||
view.findViewById<LinearLayout>(R.id.language_text).setOnClickListener {
|
||||
LanguageSettingsDialog(view.context, infos, fragment, disableSwitches, { setupDetailsTextAndSwitch() }).show()
|
||||
}
|
||||
setupDetailsTextAndSwitch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
package org.dslul.openboard.inputmethod.latin.settings
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
|
||||
import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter
|
||||
import org.dslul.openboard.inputmethod.latin.R
|
||||
import org.dslul.openboard.inputmethod.latin.common.FileUtils
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils
|
||||
import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader
|
||||
import org.dslul.openboard.inputmethod.latin.utils.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
@Suppress("deprecation")
|
||||
class LanguageSettingsDialog(
|
||||
context: Context,
|
||||
private val subtypes: MutableList<SubtypeInfo>,
|
||||
private val fragment: LanguageSettingsFragment?,
|
||||
private val disableSwitches: Boolean,
|
||||
private val onSubtypesChanged: () -> Unit
|
||||
) : AlertDialog(ContextThemeWrapper(context, R.style.platformDialogTheme)), LanguageSettingsFragment.Listener {
|
||||
private val context = ContextThemeWrapper(context, R.style.platformDialogTheme)
|
||||
private val prefs = DeviceProtectedUtils.getSharedPreferences(context)!!
|
||||
private val view = LayoutInflater.from(context).inflate(R.layout.locale_settings_dialog, null)
|
||||
private val mainLocaleString = subtypes.first().subtype.locale
|
||||
private val mainLocale = mainLocaleString.toLocale()
|
||||
private val cachedDictionaryFile by lazy { File(context.cacheDir.path + File.separator + "temp_dict") }
|
||||
|
||||
init {
|
||||
setTitle(subtypes.first().displayName)
|
||||
setView(ScrollView(context).apply { addView(view) })
|
||||
setButton(BUTTON_NEGATIVE, context.getString(R.string.dialog_close)) { _, _ ->
|
||||
dismiss()
|
||||
}
|
||||
|
||||
fillSubtypesView(view.findViewById(R.id.subtypes))
|
||||
fillSecondaryLocaleView(view.findViewById(R.id.secondary_languages))
|
||||
fillDictionariesView(view.findViewById(R.id.dictionaries))
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
fragment?.setListener(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
fragment?.setListener(null)
|
||||
}
|
||||
|
||||
private fun fillSubtypesView(subtypesView: LinearLayout) {
|
||||
if (subtypes.any { it.subtype.isAsciiCapable }) { // currently can only add subtypes for latin keyboards
|
||||
subtypesView.findViewById<ImageView>(R.id.add_subtype).setOnClickListener {
|
||||
val layouts = context.resources.getStringArray(R.array.predefined_layouts)
|
||||
.filterNot { layoutName -> subtypes.any { SubtypeLocaleUtils.getKeyboardLayoutSetName(it.subtype) == layoutName } }
|
||||
val displayNames = layouts.map { SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it) }
|
||||
Builder(context)
|
||||
.setTitle(R.string.keyboard_layout_set)
|
||||
.setItems(displayNames.toTypedArray()) { di, i ->
|
||||
di.dismiss()
|
||||
val newSubtype = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(mainLocaleString, layouts[i])
|
||||
val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context.resources, true) // enabled by default, because why else add them
|
||||
addSubtypeToView(newSubtypeInfo, subtypesView)
|
||||
val oldAdditionalSubtypesString = Settings.readPrefAdditionalSubtypes(prefs, context.resources)
|
||||
val oldAdditionalSubtypes = AdditionalSubtypeUtils.createAdditionalSubtypesArray(oldAdditionalSubtypesString).toHashSet()
|
||||
val newAdditionalSubtypesString = AdditionalSubtypeUtils.createPrefSubtypes((oldAdditionalSubtypes + newSubtype).toTypedArray())
|
||||
Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString)
|
||||
addEnabledSubtype(prefs, newSubtype)
|
||||
subtypes.add(newSubtypeInfo)
|
||||
onSubtypesChanged()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
} else
|
||||
subtypesView.findViewById<View>(R.id.add_subtype).isGone = true
|
||||
|
||||
// add subtypes
|
||||
subtypes.sortedBy { it.displayName }.forEach {
|
||||
addSubtypeToView(it, subtypesView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addSubtypeToView(subtype: SubtypeInfo, subtypesView: LinearLayout) {
|
||||
val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView)
|
||||
row.findViewById<TextView>(R.id.language_name).text =
|
||||
SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype.subtype)
|
||||
?: SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype.subtype)
|
||||
row.findViewById<View>(R.id.language_details).isGone = true
|
||||
row.findViewById<Switch>(R.id.language_switch).apply {
|
||||
isChecked = subtype.isEnabled
|
||||
isEnabled = !disableSwitches
|
||||
setOnCheckedChangeListener { _, b ->
|
||||
if (b)
|
||||
addEnabledSubtype(prefs, subtype.subtype)
|
||||
else
|
||||
removeEnabledSubtype(prefs, subtype.subtype)
|
||||
subtype.isEnabled = b
|
||||
onSubtypesChanged()
|
||||
}
|
||||
}
|
||||
if (isAdditionalSubtype(subtype.subtype)) {
|
||||
row.findViewById<Switch>(R.id.language_switch).isEnabled = true
|
||||
row.findViewById<ImageView>(R.id.delete_button).apply {
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
// can be re-added easily, no need for confirmation dialog
|
||||
subtypesView.removeView(row)
|
||||
subtypes.remove(subtype)
|
||||
|
||||
val oldAdditionalSubtypesString = Settings.readPrefAdditionalSubtypes(prefs, context.resources)
|
||||
val oldAdditionalSubtypes = AdditionalSubtypeUtils.createAdditionalSubtypesArray(oldAdditionalSubtypesString)
|
||||
val newAdditionalSubtypes = oldAdditionalSubtypes.filter { it != subtype.subtype }
|
||||
val newAdditionalSubtypesString = AdditionalSubtypeUtils.createPrefSubtypes(newAdditionalSubtypes.toTypedArray())
|
||||
Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString)
|
||||
removeEnabledSubtype(prefs, subtype.subtype)
|
||||
onSubtypesChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
subtypesView.addView(row)
|
||||
}
|
||||
|
||||
private fun fillSecondaryLocaleView(secondaryLocalesView: LinearLayout) {
|
||||
// can only use multilingual typing if there is more than one dictionary available
|
||||
val availableSecondaryLocales = getAvailableDictionaryLocales(
|
||||
context,
|
||||
mainLocaleString,
|
||||
subtypes.first().subtype.isAsciiCapable
|
||||
)
|
||||
val selectedSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocaleString)
|
||||
selectedSecondaryLocales.forEach {
|
||||
addSecondaryLocaleView(it, secondaryLocalesView)
|
||||
}
|
||||
if (availableSecondaryLocales.isNotEmpty()) {
|
||||
secondaryLocalesView.findViewById<ImageView>(R.id.add_secondary_language).apply {
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
val locales = (availableSecondaryLocales - Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() }).sorted()
|
||||
val localeNames = locales.map { it.toLocale().getDisplayName(context.resources.configuration.locale) }.toTypedArray()
|
||||
Builder(context)
|
||||
.setTitle(R.string.language_selection_title)
|
||||
.setItems(localeNames) { di, i ->
|
||||
val locale = locales[i]
|
||||
val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() }
|
||||
Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings + locale)
|
||||
addSecondaryLocaleView(locale.toLocale(), secondaryLocalesView)
|
||||
di.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
} else if (selectedSecondaryLocales.isEmpty())
|
||||
secondaryLocalesView.isGone = true
|
||||
}
|
||||
|
||||
private fun addSecondaryLocaleView(locale: Locale, secondaryLocalesView: LinearLayout) {
|
||||
val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView)
|
||||
row.findViewById<Switch>(R.id.language_switch).isGone = true
|
||||
row.findViewById<Switch>(R.id.language_details).isGone = true
|
||||
row.findViewById<TextView>(R.id.language_name).text = locale.displayName
|
||||
row.findViewById<ImageView>(R.id.delete_button).apply {
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() }
|
||||
Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings - locale.toString())
|
||||
secondaryLocalesView.removeView(row)
|
||||
}
|
||||
}
|
||||
secondaryLocalesView.addView(row)
|
||||
}
|
||||
|
||||
private fun fillDictionariesView(dictionariesView: LinearLayout) {
|
||||
dictionariesView.findViewById<ImageView>(R.id.add_dictionary).setOnClickListener {
|
||||
val link = "<a href='$DICTIONARY_URL'>" + context.getString(R.string.dictionary_link_text) + "</a>"
|
||||
val message = Html.fromHtml(context.getString(R.string.add_dictionary, link))
|
||||
val dialog = Builder(context)
|
||||
.setTitle(R.string.add_new_dictionary_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.user_dict_settings_add_menu_title) { _, _ -> fragment?.requestDictionary() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
dialog.show()
|
||||
(dialog.findViewById<View>(android.R.id.message) as? TextView)?.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
val (userDicts, hasInternalDict) = getUserAndInternalDictionaries(context, mainLocaleString)
|
||||
if (hasInternalDict) {
|
||||
dictionariesView.addView(TextView(context, null, R.style.PreferenceCategoryTitleText).apply {
|
||||
setText(R.string.internal_dictionary_summary)
|
||||
textSize *= 0.8f
|
||||
setPadding((context.resources.displayMetrics.scaledDensity * 16).toInt(), 0, 0, 0)
|
||||
isEnabled = userDicts.none { it.name == "${DictionaryInfoUtils.MAIN_DICT_PREFIX}${DictionarySettingsFragment.USER_DICTIONARY_SUFFIX}" }
|
||||
})
|
||||
}
|
||||
userDicts.sorted().forEach {
|
||||
addDictionaryToView(it, dictionariesView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewDictionary(uri: Uri?) {
|
||||
if (uri == null)
|
||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
||||
|
||||
cachedDictionaryFile.delete()
|
||||
try {
|
||||
FileUtils.copyStreamToNewFile(
|
||||
context.contentResolver.openInputStream(uri),
|
||||
cachedDictionaryFile
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
||||
}
|
||||
val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(cachedDictionaryFile, 0, cachedDictionaryFile.length())
|
||||
?: return onDictionaryLoadingError(R.string.dictionary_file_error)
|
||||
|
||||
val locale = newHeader.mLocaleString.toLocale()
|
||||
// ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not,
|
||||
// e.g. for Persian or Chinese. But at least fail when dictionary certainly is incompatible
|
||||
if (ScriptUtils.getScriptFromSpellCheckerLocale(locale) != ScriptUtils.getScriptFromSpellCheckerLocale(mainLocale))
|
||||
return onDictionaryLoadingError(R.string.dictionary_file_wrong_script)
|
||||
|
||||
if (locale != mainLocale) {
|
||||
val message = context.resources.getString(
|
||||
R.string.dictionary_file_wrong_locale,
|
||||
locale.getDisplayName(context.resources.configuration.locale),
|
||||
mainLocale.getDisplayName(context.resources.configuration.locale)
|
||||
)
|
||||
Builder(context)
|
||||
.setMessage(message)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() }
|
||||
.setPositiveButton(R.string.dictionary_file_wrong_locale_ok) { _, _ ->
|
||||
addDictAndAskToReplace(newHeader)
|
||||
}
|
||||
.show()
|
||||
return
|
||||
}
|
||||
addDictAndAskToReplace(newHeader)
|
||||
}
|
||||
|
||||
private fun addDictAndAskToReplace(header: DictionaryHeader) {
|
||||
val dictionaryType = header.mIdString.substringBefore(":")
|
||||
val dictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocaleString, context) +
|
||||
File.separator + dictionaryType + "_" + DictionarySettingsFragment.USER_DICTIONARY_SUFFIX
|
||||
val dictFile = File(dictFilename)
|
||||
|
||||
fun moveDict(replaced: Boolean) {
|
||||
if (!cachedDictionaryFile.renameTo(dictFile)) {
|
||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
||||
}
|
||||
if (dictionaryType == DictionaryInfoUtils.DEFAULT_MAIN_DICT) {
|
||||
// replaced main dict, remove the one created from internal data
|
||||
val internalMainDictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(this.toString(), context) +
|
||||
File.separator + DictionaryInfoUtils.getMainDictFilename(this.toString())
|
||||
File(internalMainDictFilename).delete()
|
||||
}
|
||||
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
||||
fragment?.activity?.sendBroadcast(newDictBroadcast)
|
||||
if (!replaced)
|
||||
addDictionaryToView(dictFile, view.findViewById(R.id.dictionaries))
|
||||
}
|
||||
|
||||
if (!dictFile.exists()) {
|
||||
return moveDict(false)
|
||||
}
|
||||
confirmDialog(context, context.getString(R.string.replace_dictionary_message2, dictionaryType), context.getString(
|
||||
R.string.replace_dictionary)) {
|
||||
moveDict(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDictionaryLoadingError(messageId: Int) = onDictionaryLoadingError(context.getString(messageId))
|
||||
|
||||
private fun onDictionaryLoadingError(message: String) {
|
||||
cachedDictionaryFile.delete()
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
private fun addDictionaryToView(dictFile: File, dictionariesView: LinearLayout) {
|
||||
val dictType = dictFile.name.substringBefore("_${DictionarySettingsFragment.USER_DICTIONARY_SUFFIX}")
|
||||
val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView)
|
||||
row.findViewById<TextView>(R.id.language_name).text = dictType
|
||||
row.findViewById<TextView>(R.id.language_details).apply {
|
||||
val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(dictFile, 0, dictFile.length())
|
||||
if (header?.description == null) {
|
||||
isGone = true
|
||||
} else {
|
||||
// what would potentially be interesting? locale? description? version? timestamp?
|
||||
text = header.description
|
||||
}
|
||||
}
|
||||
row.findViewById<Switch>(R.id.language_switch).isGone = true
|
||||
row.findViewById<ImageView>(R.id.delete_button).apply {
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
confirmDialog(context, context.getString(R.string.remove_dictionary_message2, dictType), context.getString(
|
||||
R.string.delete_dict)) {
|
||||
val parent = dictFile.parentFile
|
||||
dictFile.delete()
|
||||
if (parent?.list()?.isEmpty() == true)
|
||||
parent.delete()
|
||||
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
||||
fragment?.activity?.sendBroadcast(newDictBroadcast)
|
||||
dictionariesView.removeView(row)
|
||||
}
|
||||
}
|
||||
}
|
||||
dictionariesView.addView(row)
|
||||
}
|
||||
}
|
||||
|
||||
fun confirmDialog(context: Context, message: String, confirmButton: String, onConfirmed: (() -> Unit)) {
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(message)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(confirmButton) { _, _ -> onConfirmed() }
|
||||
.show()
|
||||
}
|
||||
|
||||
/** @return list of user dictionary files and whether an internal dictionary exists */
|
||||
fun getUserAndInternalDictionaries(context: Context, locale: String): Pair<List<File>, Boolean> {
|
||||
val localeString = locale.lowercase() // internal files and folders always use lowercase
|
||||
val userDicts = mutableListOf<File>()
|
||||
var hasInternalDict = false
|
||||
val userLocaleDir = File(DictionaryInfoUtils.getWordListCacheDirectory(context), localeString)
|
||||
if (userLocaleDir.exists() && userLocaleDir.isDirectory) {
|
||||
userLocaleDir.listFiles()?.forEach {
|
||||
if (it.name.endsWith(DictionarySettingsFragment.USER_DICTIONARY_SUFFIX))
|
||||
userDicts.add(it)
|
||||
else if (it.name.startsWith(DictionaryInfoUtils.MAIN_DICT_PREFIX))
|
||||
hasInternalDict = true
|
||||
}
|
||||
}
|
||||
if (hasInternalDict)
|
||||
return userDicts to true
|
||||
BinaryDictionaryGetter.getAssetsDictionaryList(context)?.forEach { dictFile ->
|
||||
BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictFile)?.let {
|
||||
if (it == localeString)
|
||||
return userDicts to true
|
||||
}
|
||||
}
|
||||
return userDicts to false
|
||||
}
|
||||
|
||||
// get locales with same script as main locale, but different language
|
||||
private fun getAvailableDictionaryLocales(context: Context, mainLocaleString: String, asciiCapable: Boolean): Set<String> {
|
||||
val mainLocale = mainLocaleString.toLocale()
|
||||
val locales = HashSet<String>()
|
||||
val mainScript = if (asciiCapable) ScriptUtils.SCRIPT_LATIN
|
||||
else ScriptUtils.getScriptFromSpellCheckerLocale(mainLocale)
|
||||
// ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not
|
||||
// e.g. for persian or chinese
|
||||
// workaround: don't allow secondary locales for these locales
|
||||
if (!asciiCapable && mainScript == ScriptUtils.SCRIPT_LATIN) return locales
|
||||
|
||||
// get cached dictionaries: extracted or user-added dictionaries
|
||||
val cachedDirectoryList = DictionaryInfoUtils.getCachedDirectoryList(context)
|
||||
if (cachedDirectoryList != null) {
|
||||
for (directory in cachedDirectoryList) {
|
||||
if (!directory.isDirectory) continue
|
||||
val dirLocale = DictionaryInfoUtils.getWordListIdFromFileName(directory.name)
|
||||
if (dirLocale == mainLocaleString) continue
|
||||
val locale = dirLocale.toLocale()
|
||||
if (locale.language == mainLocale.language) continue
|
||||
val localeScript = ScriptUtils.getScriptFromSpellCheckerLocale(locale)
|
||||
if (localeScript != mainScript) continue
|
||||
locales.add(locale.toString())
|
||||
}
|
||||
}
|
||||
// get assets dictionaries
|
||||
val assetsDictionaryList = BinaryDictionaryGetter.getAssetsDictionaryList(context)
|
||||
if (assetsDictionaryList != null) {
|
||||
for (dictionary in assetsDictionaryList) {
|
||||
val dictLocale =
|
||||
BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictionary)
|
||||
?: continue
|
||||
if (dictLocale == mainLocaleString) continue
|
||||
val locale = dictLocale.toLocale()
|
||||
if (locale.language == mainLocale.language) continue
|
||||
val localeScript = ScriptUtils.getScriptFromSpellCheckerLocale(locale)
|
||||
if (localeScript != mainScript) continue
|
||||
locales.add(locale.toString())
|
||||
}
|
||||
}
|
||||
return locales
|
||||
}
|
||||
|
||||
private fun String.toLocale() = LocaleUtils.constructLocaleFromString(this)
|
||||
private const val DICTIONARY_URL = "https://codeberg.org/Helium314/aosp-dictionaries"
|
|
@ -0,0 +1,188 @@
|
|||
package org.dslul.openboard.inputmethod.latin.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.preference.TwoStatePreference
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
import org.dslul.openboard.inputmethod.latin.R
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@Suppress("Deprecation") // yes everything here is deprecated, but only work on this if really necessary
|
||||
class LanguageSettingsFragment : SubScreenFragment() {
|
||||
|
||||
private val sortedSubtypes = LinkedHashMap<String, MutableList<SubtypeInfo>>()
|
||||
private val enabledSubtypes = mutableListOf<InputMethodSubtype>()
|
||||
private val systemLocales = mutableListOf<Locale>()
|
||||
private val languageFilterListPreference by lazy { findPreference("pref_language_filter") as LanguageFilterListPreference }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
addPreferencesFromResource(R.xml.prefs_screen_languages);
|
||||
SubtypeLocaleUtils.init(activity)
|
||||
|
||||
enabledSubtypes.addAll(getExplicitlyEnabledSubtypes())
|
||||
systemLocales.addAll(getSystemLocales())
|
||||
val systemLocalesSwitch = findPreference(Settings.PREF_USE_SYSTEM_LOCALES) as TwoStatePreference
|
||||
systemLocalesSwitch.setOnPreferenceChangeListener { _, b ->
|
||||
loadSubtypes(b as Boolean)
|
||||
true
|
||||
}
|
||||
loadSubtypes(systemLocalesSwitch.isChecked)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
languageFilterListPreference.setSettingsFragment(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
languageFilterListPreference.setSettingsFragment(null)
|
||||
}
|
||||
|
||||
private fun loadSubtypes(systemOnly: Boolean) {
|
||||
sortedSubtypes.clear()
|
||||
val allSubtypes = getAllAvailableSubtypes().toMutableList()
|
||||
// maybe make use of the map used by SubtypeSettings for performance reasons?
|
||||
fun List<Locale>.sortedAddToSubtypesAndRemoveFromAllSubtypes() {
|
||||
val subtypesToAdd = mutableListOf<SubtypeInfo>()
|
||||
forEach { locale ->
|
||||
val localeString = locale.toString()
|
||||
val iter = allSubtypes.iterator()
|
||||
var added = false
|
||||
while (iter.hasNext()) {
|
||||
val subtype = iter.next()
|
||||
if (subtype.locale == localeString) {
|
||||
subtypesToAdd.add(subtype.toSubtypeInfo(locale))
|
||||
iter.remove()
|
||||
added = true
|
||||
}
|
||||
}
|
||||
if (!added && locale.country.isNotEmpty()) {
|
||||
// try again, but with language only
|
||||
val languageString = locale.language
|
||||
val iter = allSubtypes.iterator()
|
||||
while (iter.hasNext()) {
|
||||
val subtype = iter.next()
|
||||
if (subtype.locale == languageString) {
|
||||
subtypesToAdd.add(subtype.toSubtypeInfo(LocaleUtils.constructLocaleFromString(languageString)))
|
||||
iter.remove()
|
||||
added = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// special treatment for the known languages with _ZZ types
|
||||
// todo: later: make it a bit less weird... and probably faster
|
||||
// consider that more _ZZ languages might be added (e.g. hinglish)
|
||||
if (!added && locale.language == "sr") {
|
||||
val languageString = locale.language
|
||||
val iter = allSubtypes.iterator()
|
||||
while (iter.hasNext()) {
|
||||
val subtype = iter.next()
|
||||
if (subtype.locale.substringBefore("_") == languageString) {
|
||||
subtypesToAdd.add(subtype.toSubtypeInfo(LocaleUtils.constructLocaleFromString(subtype.locale)))
|
||||
iter.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
subtypesToAdd.sortedBy { it.displayName }.addToSortedSubtypes()
|
||||
}
|
||||
|
||||
if (systemOnly) {
|
||||
systemLocales.sortedAddToSubtypesAndRemoveFromAllSubtypes()
|
||||
languageFilterListPreference.setLanguages(sortedSubtypes.values, systemOnly)
|
||||
return
|
||||
}
|
||||
|
||||
// add enabled subtypes
|
||||
enabledSubtypes.map { it.toSubtypeInfo(LocaleUtils.constructLocaleFromString(it.locale), true) }
|
||||
.sortedBy { it.displayName }.addToSortedSubtypes()
|
||||
allSubtypes.removeAll(enabledSubtypes)
|
||||
|
||||
// add subtypes that have a dictionary
|
||||
val localesWithDictionary = DictionaryInfoUtils.getCachedDirectoryList(activity)?.mapNotNull { dir ->
|
||||
if (!dir.isDirectory)
|
||||
return@mapNotNull null
|
||||
if (dir.list()?.any { it.endsWith(DictionarySettingsFragment.USER_DICTIONARY_SUFFIX) } == true)
|
||||
LocaleUtils.constructLocaleFromString(dir.name)
|
||||
else null
|
||||
}
|
||||
localesWithDictionary?.sortedAddToSubtypesAndRemoveFromAllSubtypes()
|
||||
|
||||
// add subtypes for device locales
|
||||
systemLocales.sortedAddToSubtypesAndRemoveFromAllSubtypes()
|
||||
|
||||
// add the remaining ones
|
||||
allSubtypes.map { it.toSubtypeInfo(LocaleUtils.constructLocaleFromString(it.locale)) }
|
||||
.sortedBy { if (it.subtype.locale.equals("zz", true))
|
||||
"zz" // "No language (Alphabet)" should be last
|
||||
else it.displayName
|
||||
}.addToSortedSubtypes()
|
||||
|
||||
// set languages
|
||||
languageFilterListPreference.setLanguages(sortedSubtypes.values, systemOnly)
|
||||
}
|
||||
|
||||
private fun InputMethodSubtype.toSubtypeInfo(locale: Locale, isEnabled: Boolean = false) =
|
||||
toSubtypeInfo(locale, resources, isEnabled)
|
||||
|
||||
private fun List<SubtypeInfo>.addToSortedSubtypes() {
|
||||
forEach {
|
||||
sortedSubtypes.getOrPut(it.displayName) { mutableListOf() }.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onNewDictionary(uri: Uri?)
|
||||
}
|
||||
|
||||
private var listener: Listener? = null
|
||||
|
||||
fun setListener(newListener: Listener?) {
|
||||
listener = newListener
|
||||
}
|
||||
|
||||
fun requestDictionary() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/octet-stream")
|
||||
startActivityForResult(intent, DICTIONARY_REQUEST_CODE)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK && requestCode == DICTIONARY_REQUEST_CODE)
|
||||
listener?.onNewDictionary(resultData?.data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SubtypeInfo(val displayName: String, val subtype: InputMethodSubtype, var isEnabled: Boolean) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is SubtypeInfo) return false
|
||||
return subtype == other.subtype
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return subtype.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
fun InputMethodSubtype.toSubtypeInfo(locale: Locale, resources: Resources, isEnabled: Boolean): SubtypeInfo {
|
||||
val displayName = if (locale.toString().equals("zz", true)) // no language
|
||||
SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(locale.toString())
|
||||
else if (locale.toString().endsWith("zz", true)) // serbian (latin), maybe others in the future
|
||||
SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this)
|
||||
else
|
||||
locale.getDisplayName(resources.configuration.locale)
|
||||
return SubtypeInfo(displayName, this, isEnabled)
|
||||
}
|
||||
|
||||
private const val DICTIONARY_REQUEST_CODE = 96834
|
|
@ -109,7 +109,7 @@ public final class SecondaryLocaleSettingsFragment extends SubScreenFragment {
|
|||
locale = "";
|
||||
final Set<String> encodedLocales = new HashSet<>();
|
||||
boolean updated = false;
|
||||
for (String encodedLocale : getSharedPreferences().getStringSet(Settings.PREF_SECONDARY_LOCALES, new HashSet<>())) {
|
||||
for (String encodedLocale : getSharedPreferences().getStringSet(Settings.PREF_SECONDARY_LOCALES_PREFIX, new HashSet<>())) {
|
||||
String[] locs = encodedLocale.split("§");
|
||||
if (locs.length == 2 && locs[0].equals(mainLocale)) {
|
||||
if (!locale.isEmpty())
|
||||
|
@ -121,7 +121,7 @@ public final class SecondaryLocaleSettingsFragment extends SubScreenFragment {
|
|||
}
|
||||
if (!updated)
|
||||
encodedLocales.add(mainLocale + "§" + locale);
|
||||
getSharedPreferences().edit().putStringSet(Settings.PREF_SECONDARY_LOCALES, encodedLocales).apply();
|
||||
getSharedPreferences().edit().putStringSet(Settings.PREF_SECONDARY_LOCALES_PREFIX, encodedLocales).apply();
|
||||
final Intent newDictBroadcast = new Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
|
||||
getActivity().sendBroadcast(newDictBroadcast);
|
||||
resetKeyboardLocales();
|
||||
|
|
|
@ -43,7 +43,6 @@ import org.dslul.openboard.inputmethod.latin.utils.StatsUtils;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
@ -146,11 +145,13 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
public static final String PREF_ENABLE_CLIPBOARD_HISTORY = "pref_enable_clipboard_history";
|
||||
public static final String PREF_CLIPBOARD_HISTORY_RETENTION_TIME = "pref_clipboard_history_retention_time";
|
||||
|
||||
public static final String PREF_SECONDARY_LOCALES = "pref_secondary_locales";
|
||||
public static final String PREF_SECONDARY_LOCALES_PREFIX = "pref_secondary_locales_";
|
||||
public static final String PREF_ADD_TO_PERSONAL_DICTIONARY = "pref_add_to_personal_dictionary";
|
||||
public static final String PREF_NAVBAR_COLOR = "pref_navbar_color";
|
||||
|
||||
public static final String PREF_NARROW_KEY_GAPS = "pref_narrow_key_gaps";
|
||||
public static final String PREF_ENABLED_INPUT_STYLES = "pref_enabled_input_styles";
|
||||
public static final String PREF_SELECTED_INPUT_STYLE = "pref_selected_input_style";
|
||||
public static final String PREF_USE_SYSTEM_LOCALES = "pref_use_system_locales";
|
||||
|
||||
// This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
|
||||
// This is being used only for the backward compatibility.
|
||||
|
@ -219,6 +220,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
} finally {
|
||||
mSettingsValuesLock.unlock();
|
||||
}
|
||||
if (key.equals(PREF_CUSTOM_INPUT_STYLES)) {
|
||||
final String additionalSubtypes = readPrefAdditionalSubtypes(prefs, mContext.getResources());
|
||||
SubtypeSettingsKt.updateAdditionalSubtypes(AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypes));
|
||||
}
|
||||
}
|
||||
|
||||
public void loadSettings(final Context context, final Locale locale,
|
||||
|
@ -544,15 +549,27 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID, defValue);
|
||||
}
|
||||
|
||||
// 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<>());
|
||||
for (String loc : encodedLocales) {
|
||||
String[] locales = loc.split("§");
|
||||
if (locales.length == 2 && locales[0].equals(mainLocaleString.toLowerCase(Locale.ENGLISH)))
|
||||
return new ArrayList<Locale>() {{ add(LocaleUtils.constructLocaleFromString(locales[1])); }};
|
||||
final String localesString = prefs.getString(PREF_SECONDARY_LOCALES_PREFIX + mainLocaleString.toLowerCase(Locale.ROOT), "");
|
||||
|
||||
final ArrayList<Locale> locales = new ArrayList<>();
|
||||
for (String locale : localesString.split(";")) {
|
||||
if (locale.isEmpty()) continue;
|
||||
locales.add(LocaleUtils.constructLocaleFromString(locale));
|
||||
}
|
||||
return new ArrayList<>();
|
||||
return locales;
|
||||
}
|
||||
|
||||
public static void setSecondaryLocales(final SharedPreferences prefs, final String mainLocaleString, final List<String> locales) {
|
||||
if (locales.isEmpty()) {
|
||||
prefs.edit().putString(PREF_SECONDARY_LOCALES_PREFIX + mainLocaleString.toLowerCase(Locale.ROOT), "").apply();
|
||||
return;
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (String locale : locales) {
|
||||
sb.append(";").append(locale);
|
||||
}
|
||||
prefs.edit().putString(PREF_SECONDARY_LOCALES_PREFIX + mainLocaleString.toLowerCase(Locale.ROOT), sb.toString()).apply();
|
||||
}
|
||||
|
||||
public static Colors getColors(final Context context, final SharedPreferences prefs) {
|
||||
|
|
|
@ -29,15 +29,18 @@ import android.provider.Settings.Secure;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.BuildConfig;
|
||||
import org.dslul.openboard.inputmethod.latin.R;
|
||||
import org.dslul.openboard.inputmethod.latin.common.FileUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.ApplicationUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.FeedbackUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.utils.JniUtils;
|
||||
import org.dslul.openboard.inputmethodcommon.InputMethodSettingsFragment;
|
||||
|
||||
import java.util.List;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -83,6 +86,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment {
|
|||
if (actionBar != null && screenTitle != null) {
|
||||
actionBar.setTitle(screenTitle);
|
||||
}
|
||||
findPreference("screen_languages").setSummary(getEnabledSubtypesLabel());
|
||||
if (BuildConfig.DEBUG)
|
||||
askAboutCrashReports();
|
||||
}
|
||||
|
@ -129,11 +133,20 @@ public final class SettingsFragment extends InputMethodSettingsFragment {
|
|||
return Secure.getInt(activity.getContentResolver(), "user_setup_complete", 0) != 0;
|
||||
}
|
||||
|
||||
private String getEnabledSubtypesLabel() {
|
||||
final List<InputMethodSubtype> subtypes = SubtypeSettingsKt.getEnabledSubtypes(DeviceProtectedUtils.getSharedPreferences(getActivity()), true);
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (final InputMethodSubtype subtype : subtypes) {
|
||||
if (sb.length() > 0)
|
||||
sb.append(", ");
|
||||
sb.append(subtype.getDisplayName(getActivity(), getActivity().getPackageName(), getActivity().getApplicationInfo()));
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
private void askAboutCrashReports() {
|
||||
// find crash report files
|
||||
final File dir = getActivity().getExternalFilesDir(null);
|
||||
if (dir == null) return;
|
||||
// final File[] files = dir.listFiles((file, s) -> file.getName().startsWith("crash_report"));
|
||||
final File[] allFiles = dir.listFiles();
|
||||
if (allFiles == null) return;
|
||||
crashReportFiles.clear();
|
||||
|
|
|
@ -252,7 +252,7 @@ public class SettingsValues {
|
|||
mClipboardHistoryRetentionTime = Settings.readClipboardHistoryRetentionTime(prefs, res);
|
||||
mOneHandedModeEnabled = Settings.readOneHandedModeEnabled(prefs);
|
||||
mOneHandedModeGravity = Settings.readOneHandedModeGravity(prefs);
|
||||
mSecondaryLocales = Settings.getSecondaryLocales(prefs, RichInputMethodManager.getInstance().getCurrentSubtypeLocale().toString());
|
||||
mSecondaryLocales = Settings.getSecondaryLocales(prefs, SubtypeSettingsKt.getSelectedSubtype(prefs).getLocale());
|
||||
|
||||
mColors = Settings.getColors(context, prefs);
|
||||
mColors.createColorFilters(prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false));
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
package org.dslul.openboard.inputmethod.latin.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
import androidx.core.app.LocaleManagerCompat
|
||||
import androidx.core.content.edit
|
||||
import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher
|
||||
import org.dslul.openboard.inputmethod.latin.BuildConfig
|
||||
import org.dslul.openboard.inputmethod.latin.R
|
||||
import org.dslul.openboard.inputmethod.latin.RichInputMethodManager
|
||||
import org.dslul.openboard.inputmethod.latin.utils.AdditionalSubtypeUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.LinkedHashMap
|
||||
|
||||
/** @return enabled subtypes. If no subtypes are enabled, but a contextForFallback is provided,
|
||||
* subtypes for system locales will be returned, or en_US if none found. */
|
||||
fun getEnabledSubtypes(prefs: SharedPreferences, fallback: Boolean = false): List<InputMethodSubtype> {
|
||||
require(initialized)
|
||||
if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true))
|
||||
return getDefaultEnabledSubtypes()
|
||||
return getExplicitlyEnabledSubtypes(fallback)
|
||||
}
|
||||
|
||||
fun getExplicitlyEnabledSubtypes(fallback: Boolean = false): List<InputMethodSubtype> {
|
||||
require(initialized)
|
||||
if (fallback && enabledSubtypes.isEmpty())
|
||||
return getDefaultEnabledSubtypes()
|
||||
return enabledSubtypes
|
||||
}
|
||||
|
||||
fun getAllAvailableSubtypes(): List<InputMethodSubtype> {
|
||||
require(initialized)
|
||||
return resourceSubtypesByLocale.values.flatten() + additionalSubtypes
|
||||
}
|
||||
|
||||
fun addEnabledSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
||||
require(initialized)
|
||||
val subtypeString = subtype.prefString()
|
||||
val oldSubtypeStrings = prefs.getString(Settings.PREF_ENABLED_INPUT_STYLES, "")!!.split(SUBTYPE_SEPARATOR)
|
||||
val newString = (oldSubtypeStrings + subtypeString).filter { it.isNotBlank() }.toSortedSet().joinToString(SUBTYPE_SEPARATOR)
|
||||
prefs.edit { putString(Settings.PREF_ENABLED_INPUT_STYLES, newString) }
|
||||
|
||||
if (subtype !in enabledSubtypes) {
|
||||
enabledSubtypes.add(subtype)
|
||||
enabledSubtypes.sortBy { it.locale } // for consistent order
|
||||
}
|
||||
}
|
||||
|
||||
/** returns whether subtype was actually removed, does not remove last subtype */
|
||||
fun removeEnabledSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
||||
require(initialized)
|
||||
val subtypeString = subtype.prefString()
|
||||
val oldSubtypeString = prefs.getString(Settings.PREF_ENABLED_INPUT_STYLES, "")!!
|
||||
val newString = (oldSubtypeString.split(SUBTYPE_SEPARATOR) - subtypeString).joinToString(SUBTYPE_SEPARATOR)
|
||||
if (newString == oldSubtypeString)
|
||||
return // already removed
|
||||
prefs.edit { putString(Settings.PREF_ENABLED_INPUT_STYLES, newString) }
|
||||
if (subtypeString == prefs.getString(Settings.PREF_SELECTED_INPUT_STYLE, "")) {
|
||||
// switch subtype if the currently used one has been disabled
|
||||
val nextSubtype = RichInputMethodManager.getInstance().getNextSubtypeInThisIme(true)
|
||||
if (subtypeString == nextSubtype?.prefString())
|
||||
KeyboardSwitcher.getInstance().switchToSubtype(getDefaultEnabledSubtypes().first())
|
||||
else
|
||||
KeyboardSwitcher.getInstance().switchToSubtype(nextSubtype)
|
||||
}
|
||||
enabledSubtypes.remove(subtype)
|
||||
}
|
||||
|
||||
fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype {
|
||||
require(initialized)
|
||||
val subtypeString = prefs.getString(Settings.PREF_SELECTED_INPUT_STYLE, "")!!.split(LOCALE_LAYOUT_SEPARATOR)
|
||||
val subtype = enabledSubtypes.firstOrNull { subtypeString.first() == it.locale && subtypeString.last() == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) }
|
||||
?: enabledSubtypes.firstOrNull()
|
||||
if (subtype == null) {
|
||||
val defaultSubtypes = getDefaultEnabledSubtypes()
|
||||
return defaultSubtypes.firstOrNull { subtypeString.first() == it.locale && subtypeString.last() == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) }
|
||||
?: defaultSubtypes.firstOrNull { subtypeString.first().substringBefore("_") == it.locale.substringBefore("_") && subtypeString.last() == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) }
|
||||
?: defaultSubtypes.first()
|
||||
}
|
||||
return subtype
|
||||
}
|
||||
|
||||
fun setSelectedSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
||||
val subtypeString = subtype.prefString()
|
||||
if (subtype.locale.isEmpty() || prefs.getString(Settings.PREF_SELECTED_INPUT_STYLE, "") == subtypeString)
|
||||
return
|
||||
prefs.edit { putString(Settings.PREF_SELECTED_INPUT_STYLE, subtypeString) }
|
||||
}
|
||||
|
||||
fun isAdditionalSubtype(subtype: InputMethodSubtype): Boolean {
|
||||
return subtype in additionalSubtypes
|
||||
}
|
||||
|
||||
fun updateAdditionalSubtypes(subtypes: Array<InputMethodSubtype>) {
|
||||
additionalSubtypes.clear()
|
||||
additionalSubtypes.addAll(subtypes)
|
||||
}
|
||||
|
||||
fun reloadSystemLocales(context: Context) {
|
||||
systemLocales.clear()
|
||||
val localeList = LocaleManagerCompat.getSystemLocales(context)
|
||||
(0 until localeList.size()).forEach {
|
||||
val locale = localeList[it]
|
||||
if (locale != null) systemLocales.add(locale)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSystemLocales(): List<Locale> {
|
||||
require(initialized)
|
||||
return systemLocales
|
||||
}
|
||||
|
||||
fun init(context: Context) {
|
||||
if (initialized) return
|
||||
SubtypeLocaleUtils.init(context) // necessary to get the correct getKeyboardLayoutSetName
|
||||
|
||||
// necessary to set system locales at start, because for some weird reason (bug?)
|
||||
// LocaleManagerCompat.getSystemLocales(context) sometimes doesn't return all system locales
|
||||
reloadSystemLocales(context)
|
||||
|
||||
loadResourceSubtypes(context.resources)
|
||||
loadAdditionalSubtypes(context)
|
||||
loadEnabledSubtypes(context)
|
||||
initialized = true
|
||||
}
|
||||
|
||||
private fun getDefaultEnabledSubtypes(): List<InputMethodSubtype> {
|
||||
val inputMethodSubtypes = systemLocales.mapNotNull { locale ->
|
||||
val localeString = locale.toString()
|
||||
val subtypes = resourceSubtypesByLocale[localeString]
|
||||
?: resourceSubtypesByLocale[localeString.substringBefore("_")] // fall back to language match
|
||||
subtypes?.firstOrNull() // todo: maybe set default for some languages with multiple resource subtypes?
|
||||
}
|
||||
if (inputMethodSubtypes.isEmpty())
|
||||
// hardcoded fallback for weird cases
|
||||
return listOf(resourceSubtypesByLocale["en_US"]!!.first())
|
||||
return inputMethodSubtypes
|
||||
}
|
||||
|
||||
private fun InputMethodSubtype.prefString() =
|
||||
locale + LOCALE_LAYOUT_SEPARATOR + SubtypeLocaleUtils.getKeyboardLayoutSetName(this)
|
||||
|
||||
private fun loadResourceSubtypes(resources: Resources) {
|
||||
val xml = resources.getXml(R.xml.method)
|
||||
xml.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
|
||||
val namespace = "http://schemas.android.com/apk/res/android"
|
||||
var eventType = xml.eventType
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG && xml.name == "subtype") {
|
||||
val icon = xml.getAttributeResourceValue(namespace, "icon", 0)
|
||||
val label = xml.getAttributeResourceValue(namespace, "label", 0)
|
||||
val subtypeId = xml.getAttributeIntValue(namespace, "subtypeId", 0)
|
||||
val locale = xml.getAttributeValue(namespace, "imeSubtypeLocale").intern()
|
||||
val languageTag = xml.getAttributeValue(namespace, "languageTag")
|
||||
val imeSubtypeMode = xml.getAttributeValue(namespace, "imeSubtypeMode")
|
||||
val imeSubtypeExtraValue = xml.getAttributeValue(namespace, "imeSubtypeExtraValue").intern()
|
||||
val isAsciiCapable = xml.getAttributeBooleanValue(namespace, "isAsciiCapable", false)
|
||||
val b = InputMethodSubtype.InputMethodSubtypeBuilder()
|
||||
b.setSubtypeIconResId(icon)
|
||||
b.setSubtypeNameResId(label)
|
||||
if (subtypeId != 0)
|
||||
b.setSubtypeId(subtypeId)
|
||||
b.setSubtypeLocale(locale)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && languageTag != null)
|
||||
b.setLanguageTag(languageTag)
|
||||
b.setSubtypeMode(imeSubtypeMode)
|
||||
b.setSubtypeExtraValue(imeSubtypeExtraValue)
|
||||
b.setIsAsciiCapable(isAsciiCapable)
|
||||
resourceSubtypesByLocale.getOrPut(locale) { ArrayList(2) }.add(b.build())
|
||||
}
|
||||
eventType = xml.next()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadAdditionalSubtypes(context: Context) {
|
||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||
val additionalSubtypeString = Settings.readPrefAdditionalSubtypes(prefs, context.resources)
|
||||
val subtypes = AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypeString)
|
||||
additionalSubtypes.addAll(subtypes)
|
||||
}
|
||||
|
||||
// requires loadResourceSubtypes to be called before
|
||||
private fun loadEnabledSubtypes(context: Context) {
|
||||
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
|
||||
val subtypeStrings = prefs.getString(Settings.PREF_ENABLED_INPUT_STYLES, "")!!
|
||||
.split(SUBTYPE_SEPARATOR).filter { it.isNotEmpty() }.map { it.split(LOCALE_LAYOUT_SEPARATOR) }
|
||||
|
||||
for (localeAndLayout in subtypeStrings) {
|
||||
require(localeAndLayout.size == 2)
|
||||
val subtypesForLocale = resourceSubtypesByLocale[localeAndLayout.first()]
|
||||
if (BuildConfig.DEBUG) // should not happen, but should not crash for normal user
|
||||
require(subtypesForLocale != null)
|
||||
else if (subtypesForLocale == null)
|
||||
continue
|
||||
|
||||
val subtype = subtypesForLocale.firstOrNull { SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.last() }
|
||||
?: additionalSubtypes.firstOrNull { it.locale == localeAndLayout.first() && SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.last() }
|
||||
if (BuildConfig.DEBUG) // should not happen, but should not crash for normal user
|
||||
require(subtype != null)
|
||||
else if (subtype == null)
|
||||
continue
|
||||
|
||||
enabledSubtypes.add(subtype)
|
||||
}
|
||||
}
|
||||
|
||||
private var initialized = false
|
||||
private val enabledSubtypes = mutableListOf<InputMethodSubtype>()
|
||||
private val resourceSubtypesByLocale = LinkedHashMap<String, MutableList<InputMethodSubtype>>(100)
|
||||
private val additionalSubtypes = mutableListOf<InputMethodSubtype>()
|
||||
private val systemLocales = mutableListOf<Locale>()
|
||||
|
||||
private const val SUBTYPE_SEPARATOR = ";"
|
||||
private const val LOCALE_LAYOUT_SEPARATOR = ":"
|
|
@ -180,11 +180,14 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
|
|||
(TextView)findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3),
|
||||
R.string.setup_step3_title, R.string.setup_step3_instruction,
|
||||
0 /* finishedInstruction */, R.drawable.ic_setup_step3,
|
||||
R.string.setup_step3_action);
|
||||
R.string.setup_step3_action_new);
|
||||
step3.setAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
invokeSubtypeEnablerOfThisIme();
|
||||
final Intent intent = new Intent(getApplicationContext(), SettingsActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
mSetupStepGroup.addStep(step3);
|
||||
|
@ -282,19 +285,6 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
|
|||
mNeedsToAdjustStepNumberToSystemState = true;
|
||||
}
|
||||
|
||||
void invokeSubtypeEnablerOfThisIme() {
|
||||
final InputMethodInfo imi =
|
||||
UncachedInputMethodManagerUtils.getInputMethodInfoOf(getPackageName(), mImm);
|
||||
if (imi == null) {
|
||||
return;
|
||||
}
|
||||
final Intent intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private int determineSetupStepNumberFromLauncher() {
|
||||
final int stepNumber = determineSetupStepNumber();
|
||||
if (stepNumber == STEP_1) {
|
||||
|
|
|
@ -107,29 +107,35 @@ public final class AdditionalSubtypeUtils {
|
|||
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
|
||||
final ArrayList<InputMethodSubtype> subtypesList = new ArrayList<>(prefSubtypeArray.length);
|
||||
for (final String prefSubtype : prefSubtypeArray) {
|
||||
final String[] elems = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
|
||||
if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
|
||||
&& elems.length != LENGTH_WITH_EXTRA_VALUE) {
|
||||
Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype + " in "
|
||||
+ prefSubtypes);
|
||||
continue;
|
||||
}
|
||||
final String localeString = elems[INDEX_OF_LOCALE];
|
||||
final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT];
|
||||
// Here we assume that all the additional subtypes have AsciiCapable and EmojiCapable.
|
||||
// This is actually what the setting dialog for additional subtype is doing.
|
||||
final InputMethodSubtype subtype = createAsciiEmojiCapableAdditionalSubtype(
|
||||
localeString, keyboardLayoutSetName);
|
||||
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
|
||||
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
|
||||
// layout has been removed.
|
||||
continue;
|
||||
}
|
||||
subtypesList.add(subtype);
|
||||
final InputMethodSubtype subtype = createSubtypeFromString(prefSubtype);
|
||||
if (subtype != null)
|
||||
subtypesList.add(subtype);
|
||||
}
|
||||
return subtypesList.toArray(new InputMethodSubtype[subtypesList.size()]);
|
||||
}
|
||||
|
||||
// use string created with getPrefSubtype
|
||||
public static InputMethodSubtype createSubtypeFromString(final String prefSubtype) {
|
||||
final String[] elems = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
|
||||
if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
|
||||
&& elems.length != LENGTH_WITH_EXTRA_VALUE) {
|
||||
Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype);
|
||||
return null;
|
||||
}
|
||||
final String localeString = elems[INDEX_OF_LOCALE];
|
||||
final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT];
|
||||
// Here we assume that all the additional subtypes have AsciiCapable and EmojiCapable.
|
||||
// This is actually what the setting dialog for additional subtype is doing.
|
||||
final InputMethodSubtype subtype = createAsciiEmojiCapableAdditionalSubtype(
|
||||
localeString, keyboardLayoutSetName);
|
||||
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
|
||||
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
|
||||
// layout has been removed.
|
||||
return null;
|
||||
}
|
||||
return subtype;
|
||||
}
|
||||
|
||||
public static String createPrefSubtypes(final InputMethodSubtype[] subtypes) {
|
||||
if (subtypes == null || subtypes.length == 0) {
|
||||
return "";
|
||||
|
|
|
@ -329,6 +329,7 @@ public class DictionaryInfoUtils {
|
|||
return MAIN_DICT_PREFIX + locale.toLowerCase(Locale.ENGLISH) + ".dict";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
|
||||
final long offset, final long length) {
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package org.dslul.openboard.inputmethod.latin.utils
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.os.IBinder
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.InputMethodInfo
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
import org.dslul.openboard.inputmethod.latin.LatinIME
|
||||
import org.dslul.openboard.inputmethod.latin.R
|
||||
import org.dslul.openboard.inputmethod.latin.RichInputMethodManager
|
||||
|
||||
// similar to what showSubtypePicker does in https://github.com/rkkr/simple-keyboard/blob/master/app/src/main/java/rkr/simplekeyboard/inputmethod/latin/RichInputMethodManager.java
|
||||
fun showInputMethodPicker(latinIme: LatinIME, richImm: RichInputMethodManager, windowToken: IBinder) {
|
||||
val pm = latinIme.packageManager
|
||||
val thisImi = richImm.inputMethodInfoOfThisIme
|
||||
val currentSubtype = richImm.currentSubtype.rawSubtype
|
||||
val enabledImis = richImm.inputMethodManager.enabledInputMethodList
|
||||
.sortedBy { it.hashCode() }.sortedBy { it.loadLabel(pm).toString() } // first label, then hashCode
|
||||
val enabledSubtypes = mutableListOf<Pair<InputMethodInfo, InputMethodSubtype?>>()
|
||||
var currentSubtypeIndex = 0
|
||||
enabledImis.forEach { imi ->
|
||||
val subtypes = richImm.getEnabledInputMethodSubtypeList(imi, true)
|
||||
if (subtypes.isEmpty()) {
|
||||
enabledSubtypes.add(imi to null)
|
||||
} else {
|
||||
subtypes.forEach {
|
||||
if (!it.isAuxiliary) {
|
||||
enabledSubtypes.add(imi to it)
|
||||
if (imi == thisImi && it == currentSubtype)
|
||||
currentSubtypeIndex = enabledSubtypes.lastIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val items = mutableListOf<SpannableStringBuilder>()
|
||||
for (imiAndSubtype in enabledSubtypes) {
|
||||
val (imi, subtype) = imiAndSubtype
|
||||
|
||||
val title = SpannableString(subtype?.getDisplayName(latinIme, imi.packageName, imi.serviceInfo.applicationInfo)
|
||||
?.ifBlank { imi.loadLabel(pm) }
|
||||
?: imi.loadLabel(pm))
|
||||
val subtitle = SpannableString(if (subtype == null) "" else "\n${imi.loadLabel(pm)}")
|
||||
title.setSpan(
|
||||
RelativeSizeSpan(0.9f), 0, title.length,
|
||||
Spannable.SPAN_INCLUSIVE_INCLUSIVE
|
||||
)
|
||||
subtitle.setSpan(
|
||||
RelativeSizeSpan(0.85f), 0, subtitle.length,
|
||||
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
|
||||
)
|
||||
items.add(SpannableStringBuilder().append(title).append(subtitle))
|
||||
}
|
||||
|
||||
val dialog = AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(latinIme))
|
||||
.setTitle(R.string.select_input_method)
|
||||
.setSingleChoiceItems(items.toTypedArray(), currentSubtypeIndex) { di, i ->
|
||||
di.dismiss()
|
||||
val (imi, subtype) = enabledSubtypes[i]
|
||||
if (imi == thisImi)
|
||||
latinIme.switchToSubtype(subtype)
|
||||
else if (subtype != null)
|
||||
latinIme.switchInputMethodAndSubtype(imi, subtype)
|
||||
else
|
||||
latinIme.switchInputMethod(imi.id)
|
||||
}
|
||||
.create()
|
||||
|
||||
val window = dialog.window
|
||||
val layoutParams = window?.attributes
|
||||
layoutParams?.token = windowToken
|
||||
layoutParams?.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG
|
||||
window?.attributes = layoutParams
|
||||
window?.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
|
||||
dialog.show()
|
||||
}
|
|
@ -313,14 +313,14 @@ public final class SubtypeLocaleUtils {
|
|||
return LocaleUtils.constructLocaleFromString(localeString);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
public static String getKeyboardLayoutSetDisplayName(
|
||||
@Nonnull final InputMethodSubtype subtype) {
|
||||
final String layoutName = getKeyboardLayoutSetName(subtype);
|
||||
return getKeyboardLayoutSetDisplayName(layoutName);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
public static String getKeyboardLayoutSetDisplayName(@Nonnull final String layoutName) {
|
||||
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue