From 9c3a0b7918a0429b50e2124251a71e1bb48d82bd Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Thu, 16 Mar 2023 15:08:23 +0000 Subject: [PATCH] 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 --- .../server/input/KeyboardLayoutManager.java | 43 +++++++---- .../res/xml/keyboard_layouts.xml | 8 ++ .../input/KeyboardLayoutManagerTests.kt | 77 +++++++++++++------ 3 files changed, 91 insertions(+), 37 deletions(-) diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 4d4a87e186645..72c7dadac2714 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -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) { diff --git a/services/tests/servicestests/res/xml/keyboard_layouts.xml b/services/tests/servicestests/res/xml/keyboard_layouts.xml index b5a05fcaff173..5f3fcd6eaed07 100644 --- a/services/tests/servicestests/res/xml/keyboard_layouts.xml +++ b/services/tests/servicestests/res/xml/keyboard_layouts.xml @@ -72,10 +72,18 @@ android:keyboardLayout="@raw/dummy_keyboard_layout" android:keyboardLocale="ru-Cyrl" /> + + + diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt index b660926f13946..7729fa29667b8 100644 --- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -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, layoutDesc: String): Boolean { + for (kl in layoutList) { + if (kl.descriptor.equals(layoutDesc)) { + return true } } return false