Modify Script matching algorithm

First manually match script code based on provided scripts in the
language tag before using UScript to derive script code from tag
and matching that.
Corner case: Uscript.getCode() hard codes script for "ja..." tags
to Japenese, Hirangana and Han scripts even if we provide explicit
"Latn" in the language tag. IME can explicitly say "Latn" in language
tag to allow any KCMs with Latn support and handle composition to
Japense on IME side. So, if explicitly mentioned we should respect
the script code provided.

Test: atest KeyboardLayoutManagerTests
Bug: 272200660
Change-Id: Ic0ffd3aa57d83b7fd8c1fb66ec12581f90c1deaf
This commit is contained in:
Vaibhav Devmurari
2023-03-16 15:08:23 +00:00
parent 1b485a66ab
commit 9c3a0b7918
3 changed files with 91 additions and 37 deletions

View File

@@ -1261,30 +1261,45 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
@NonNull String languageTag) {
final int[] scriptsFromLanguageTag = UScript.getCode(Locale.forLanguageTag(languageTag));
if (scriptsFromLanguageTag.length == 0) {
// If no scripts inferred from languageTag then allowing the layout
return true;
}
LocaleList locales = layout.getLocales();
if (locales.isEmpty()) {
LocaleList layoutLocales = layout.getLocales();
if (layoutLocales.isEmpty()) {
// KCM file doesn't have an associated language tag. This can be from
// a 3rd party app so need to include it as a potential layout.
return true;
}
for (int i = 0; i < locales.size(); i++) {
final Locale locale = locales.get(i);
if (locale == null) {
continue;
}
int[] scripts = UScript.getCode(locale);
if (scripts != null && haveCommonValue(scripts, scriptsFromLanguageTag)) {
// Match derived Script codes
final int[] scriptsFromLanguageTag = getScriptCodes(Locale.forLanguageTag(languageTag));
if (scriptsFromLanguageTag.length == 0) {
// If no scripts inferred from languageTag then allowing the layout
return true;
}
for (int i = 0; i < layoutLocales.size(); i++) {
final Locale locale = layoutLocales.get(i);
int[] scripts = getScriptCodes(locale);
if (haveCommonValue(scripts, scriptsFromLanguageTag)) {
return true;
}
}
return false;
}
private static int[] getScriptCodes(@Nullable Locale locale) {
if (locale == null) {
return new int[0];
}
if (!TextUtils.isEmpty(locale.getScript())) {
int scriptCode = UScript.getCodeFromName(locale.getScript());
if (scriptCode != UScript.INVALID_CODE) {
return new int[]{scriptCode};
}
}
int[] scripts = UScript.getCode(locale);
if (scripts != null) {
return scripts;
}
return new int[0];
}
private static boolean haveCommonValue(int[] arr1, int[] arr2) {
for (int a1 : arr1) {
for (int a2 : arr2) {

View File

@@ -72,10 +72,18 @@
android:keyboardLayout="@raw/dummy_keyboard_layout"
android:keyboardLocale="ru-Cyrl" />
<keyboard-layout
android:name="keyboard_layout_english_without_script_code"
android:label="English(No script code)"
android:keyboardLayout="@raw/dummy_keyboard_layout"
android:keyboardLocale="en"
android:keyboardLayoutType="qwerty" />
<keyboard-layout
android:name="keyboard_layout_vendorId:1,productId:1"
android:label="vendorId:1,productId:1"
android:keyboardLayout="@raw/dummy_keyboard_layout"
androidprv:vendorId="1"
androidprv:productId="1" />
</keyboard-layouts>

View File

@@ -26,7 +26,6 @@ import android.content.pm.ServiceInfo
import android.hardware.input.IInputManager
import android.hardware.input.InputManager
import android.hardware.input.KeyboardLayout
import android.icu.lang.UScript
import android.icu.util.ULocale
import android.os.Bundle
import android.os.test.TestLooper
@@ -52,7 +51,6 @@ import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.util.Locale
private fun createKeyboard(
deviceId: Int,
@@ -553,24 +551,17 @@ class KeyboardLayoutManagerTests {
0,
keyboardLayouts.size
)
val englishScripts = UScript.getCode(Locale.forLanguageTag("hi-Latn"))
for (kl in keyboardLayouts) {
var isCompatible = false
for (i in 0 until kl.locales.size()) {
val locale: Locale = kl.locales.get(i) ?: continue
val scripts = UScript.getCode(locale)
if (scripts != null && areScriptsCompatible(scripts, englishScripts)) {
isCompatible = true
break
}
}
assertTrue(
"New UI: getKeyboardLayoutListForInputDevice API should only return " +
"compatible layouts but found " + kl.descriptor,
isCompatible
assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
"containing English(US) layout for hi-Latn",
containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
)
assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
"containing English(No script code) layout for hi-Latn",
containsLayout(
keyboardLayouts,
createLayoutDescriptor("keyboard_layout_english_without_script_code")
)
}
)
// Check Layouts for "hi" which by default uses 'Deva' script.
keyboardLayouts =
@@ -600,6 +591,46 @@ class KeyboardLayoutManagerTests {
1,
keyboardLayouts.size
)
// Special case Japanese: UScript ignores provided script code for certain language tags
// Should manually match provided script codes and then rely on Uscript to derive
// script from language tags and match those.
keyboardLayouts =
keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
createImeSubtypeForLanguageTag("ja-Latn-JP")
)
assertNotEquals(
"New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
"supported layouts with matching script code for ja-Latn-JP",
0,
keyboardLayouts.size
)
assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
"containing English(US) layout for ja-Latn-JP",
containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
)
assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
"containing English(No script code) layout for ja-Latn-JP",
containsLayout(
keyboardLayouts,
createLayoutDescriptor("keyboard_layout_english_without_script_code")
)
)
// If script code not explicitly provided for Japanese should rely on Uscript to find
// derived script code and hence no suitable layout will be found.
keyboardLayouts =
keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
createImeSubtypeForLanguageTag("ja-JP")
)
assertEquals(
"New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
"supported layouts with matching script code for ja-JP",
0,
keyboardLayouts.size
)
}
}
@@ -779,10 +810,10 @@ class KeyboardLayoutManagerTests {
private fun createLayoutDescriptor(keyboardName: String): String =
"$PACKAGE_NAME/$RECEIVER_NAME/$keyboardName"
private fun areScriptsCompatible(scriptList1: IntArray, scriptList2: IntArray): Boolean {
for (s1 in scriptList1) {
for (s2 in scriptList2) {
if (s1 == s2) return true
private fun containsLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
for (kl in layoutList) {
if (kl.descriptor.equals(layoutDesc)) {
return true
}
}
return false