more exhaustive search for hinLocales, if present

This commit is contained in:
Helium314 2023-10-17 15:01:02 +02:00
parent 67d4f53e73
commit 1dc53663f0
5 changed files with 62 additions and 68 deletions

View file

@ -6,20 +6,12 @@
package org.dslul.openboard.inputmethod.compat package org.dslul.openboard.inputmethod.compat
import android.os.Build
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import java.util.* import java.util.*
import kotlin.collections.ArrayList
object EditorInfoCompatUtils { object EditorInfoCompatUtils {
// Note that EditorInfo.IME_FLAG_FORCE_ASCII has been introduced
// in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
private val FIELD_IME_FLAG_FORCE_ASCII = CompatUtils.getField(EditorInfo::class.java, "IME_FLAG_FORCE_ASCII")
private val OBJ_IME_FLAG_FORCE_ASCII: Int? = CompatUtils.getFieldValue(null, null, FIELD_IME_FLAG_FORCE_ASCII) as? Int
private val FIELD_HINT_LOCALES = CompatUtils.getField(EditorInfo::class.java, "hintLocales")
@JvmStatic
fun hasFlagForceAscii(imeOptions: Int): Boolean {
return if (OBJ_IME_FLAG_FORCE_ASCII == null) false else imeOptions and OBJ_IME_FLAG_FORCE_ASCII != 0
}
@JvmStatic @JvmStatic
fun imeActionName(imeOptions: Int): String { fun imeActionName(imeOptions: Int): String {
@ -36,32 +28,16 @@ object EditorInfoCompatUtils {
} }
} }
fun imeOptionsName(imeOptions: Int): String {
val action = imeActionName(imeOptions)
val flags = StringBuilder()
if (imeOptions and EditorInfo.IME_FLAG_NO_ENTER_ACTION != 0) {
flags.append("flagNoEnterAction|")
}
if (imeOptions and EditorInfo.IME_FLAG_NAVIGATE_NEXT != 0) {
flags.append("flagNavigateNext|")
}
if (imeOptions and EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS != 0) {
flags.append("flagNavigatePrevious|")
}
if (hasFlagForceAscii(imeOptions)) {
flags.append("flagForceAscii|")
}
return flags.toString() + action
}
@JvmStatic @JvmStatic
fun getPrimaryHintLocale(editorInfo: EditorInfo?): Locale? { fun getHintLocales(editorInfo: EditorInfo?): List<Locale>? {
if (editorInfo == null) { if (editorInfo == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return null return null
} }
val localeList = CompatUtils.getFieldValue(editorInfo, null, FIELD_HINT_LOCALES) ?: return null val localeList = editorInfo.hintLocales ?: return null
return if (LocaleListCompatUtils.isEmpty(localeList)) { val locales = ArrayList<Locale>(localeList.size())
null for (i in 0 until localeList.size()) {
} else LocaleListCompatUtils[localeList, 0] locales[i] = localeList.get(i)
}
return locales
} }
} }

View file

@ -1,22 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
* modified
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
*/
package org.dslul.openboard.inputmethod.compat
import java.util.*
object LocaleListCompatUtils {
private val CLASS_LocaleList = CompatUtils.getClass("android.os.LocaleList")
private val METHOD_get = CompatUtils.getMethod(CLASS_LocaleList, "get", Int::class.javaPrimitiveType)
private val METHOD_isEmpty = CompatUtils.getMethod(CLASS_LocaleList, "isEmpty")
fun isEmpty(localeList: Any?): Boolean {
return CompatUtils.invoke(localeList, java.lang.Boolean.FALSE, METHOD_isEmpty) as Boolean
}
operator fun get(localeList: Any?, index: Int): Locale? {
return CompatUtils.invoke(localeList, null, METHOD_get, index) as Locale
}
}

View file

@ -290,8 +290,7 @@ public final class KeyboardLayoutSet {
// TODO: Consolidate with {@link InputAttributes}. // TODO: Consolidate with {@link InputAttributes}.
@SuppressWarnings("deprecation") final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions( @SuppressWarnings("deprecation") final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
mPackageName, FORCE_ASCII, mParams.mEditorInfo); mPackageName, FORCE_ASCII, mParams.mEditorInfo);
final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii( final boolean forceAscii = (mParams.mEditorInfo.imeOptions & EditorInfo.IME_FLAG_FORCE_ASCII) != 0
mParams.mEditorInfo.imeOptions)
|| deprecatedForceAscii; || deprecatedForceAscii;
final RichInputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable) final RichInputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable)
? RichInputMethodSubtype.getNoLanguageSubtype() ? RichInputMethodSubtype.getNoLanguageSubtype()

View file

@ -897,19 +897,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) { void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
super.onStartInput(editorInfo, restarting); super.onStartInput(editorInfo, restarting);
// If the primary hint language does not match the current subtype language, then try final List<Locale> hintLocales = EditorInfoCompatUtils.getHintLocales(editorInfo);
// to switch to the primary hint language. if (hintLocales == null) {
// TODO: Support all the locales in EditorInfo#hintLocales.
final Locale primaryHintLocale = EditorInfoCompatUtils.getPrimaryHintLocale(editorInfo);
if (primaryHintLocale == null) {
return;
}
final InputMethodSubtype newSubtype = mRichImm.findSubtypeByLocale(primaryHintLocale);
if (newSubtype == null || newSubtype.equals(mRichImm.getCurrentSubtype().getRawSubtype())) {
return; return;
} }
// Try switching to a subtype matching the hint language.
for (final Locale hintLocale : hintLocales) {
final InputMethodSubtype newSubtype = mRichImm.findSubtypeByLocale(hintLocale);
if (newSubtype == null) continue;
if (newSubtype.equals(mRichImm.getCurrentSubtype().getRawSubtype()))
return; // no need to switch, we already use the correct locale
mHandler.postSwitchLanguage(newSubtype); mHandler.postSwitchLanguage(newSubtype);
} }
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) { void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) {

View file

@ -18,9 +18,11 @@ import android.view.inputmethod.InputMethodSubtype;
import org.dslul.openboard.inputmethod.annotations.UsedForTesting; import org.dslul.openboard.inputmethod.annotations.UsedForTesting;
import org.dslul.openboard.inputmethod.compat.InputMethodSubtypeCompatUtils; 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.settings.SubtypeSettingsKt;
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils; 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.ScriptUtils;
import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils; import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Collections; import java.util.Collections;
@ -308,10 +310,10 @@ public class RichInputMethodManager {
public InputMethodSubtype findSubtypeByLocale(final Locale locale) { public InputMethodSubtype findSubtypeByLocale(final Locale locale) {
// Find the best subtype based on a straightforward matching algorithm. // Find the best subtype based on a straightforward matching algorithm.
// TODO: Use LocaleList#getFirstMatch() instead.
final List<InputMethodSubtype> subtypes = final List<InputMethodSubtype> subtypes =
getMyEnabledInputMethodSubtypeList(true /* allowsImplicitlySelectedSubtypes */); getMyEnabledInputMethodSubtypeList(true /* allowsImplicitlySelectedSubtypes */);
final int count = subtypes.size(); final int count = subtypes.size();
// search for exact match
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(i); final InputMethodSubtype subtype = subtypes.get(i);
final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
@ -319,6 +321,7 @@ public class RichInputMethodManager {
return subtype; return subtype;
} }
} }
// search for language + country + variant match
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(i); final InputMethodSubtype subtype = subtypes.get(i);
final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
@ -328,6 +331,7 @@ public class RichInputMethodManager {
return subtype; return subtype;
} }
} }
// search for language + country match
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(i); final InputMethodSubtype subtype = subtypes.get(i);
final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
@ -336,6 +340,19 @@ public class RichInputMethodManager {
return subtype; return subtype;
} }
} }
// search for secondary locale match
final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(mContext);
for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(i);
final String subtypeLocale = subtype.getLocale();
final List<Locale> secondaryLocales = Settings.getSecondaryLocales(prefs, subtypeLocale);
for (final Locale secondaryLocale : secondaryLocales) {
if (secondaryLocale.equals(locale)) {
return subtype;
}
}
}
// search for language match
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(i); final InputMethodSubtype subtype = subtypes.get(i);
final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
@ -343,6 +360,30 @@ public class RichInputMethodManager {
return subtype; return subtype;
} }
} }
// search for secondary language match
for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(i);
final String subtypeLocale = subtype.getLocale();
final List<Locale> secondaryLocales = Settings.getSecondaryLocales(prefs, subtypeLocale);
for (final Locale secondaryLocale : secondaryLocales) {
if (secondaryLocale.getLanguage().equals(locale.getLanguage())) {
return subtype;
}
}
}
// extra: if current script is not compatible to current subtype, search for compatible script
// this is acceptable only because this function is only used for switching to a certain locale using EditorInfo.hintLocales
final int script = ScriptUtils.getScriptFromSpellCheckerLocale(locale);
if (script != ScriptUtils.getScriptFromSpellCheckerLocale(getCurrentSubtypeLocale())) {
for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(i);
final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype);
if (ScriptUtils.getScriptFromSpellCheckerLocale(subtypeLocale) == script) {
return subtype;
}
}
}
return null; return null;
} }