From 5604d48b29b345b7506b1109320e66f3f9bfdb2f Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Wed, 22 Jan 2025 10:07:30 +0000 Subject: Add support for overriding keyboard layout for a keyboard Default use case for keyboard should follow H/W country code or IME language, but for special cases like emulator where we would like to set keyboard layout based on the host device which is independent of the underlying IME configuration. Bug: 389995108 Test: manual with emulator Flag: EXEMPT bugfix Change-Id: I2baee5037c6dc45724909c5b6d2cac399ce394f5 --- .../java/android/hardware/input/IInputManager.aidl | 6 ++++ .../android/hardware/input/InputManagerGlobal.java | 39 ++++++++++++++++++++++ .../android/server/input/InputManagerService.java | 9 +++++ .../server/input/KeyboardLayoutManager.java | 35 ++++++++++++++++--- .../server/input/KeyboardLayoutManagerTests.kt | 37 ++++++++++++++++++++ 5 files changed, 121 insertions(+), 5 deletions(-) diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 2bb28a1b6b0b..7fc7e4d81afa 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -103,6 +103,12 @@ interface IInputManager { in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype); + @EnforcePermission("SET_KEYBOARD_LAYOUT") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)") + void setKeyboardLayoutOverrideForInputDevice(in InputDeviceIdentifier identifier, + String keyboardLayoutDescriptor); + @EnforcePermission("SET_KEYBOARD_LAYOUT") @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)") diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index 3ef90e4b8a5f..e79416162fc2 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -57,6 +57,8 @@ import android.view.InputMonitor; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.PointerIcon; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -1255,6 +1257,43 @@ public final class InputManagerGlobal { } } + /** + * Sets the keyboard layout override for the specified input device. This will set the + * keyboard layout as the default for the input device irrespective of the underlying IME + * configuration. + * + *

+ * Prefer using {@link InputManager#setKeyboardLayoutForInputDevice(InputDeviceIdentifier, int, + * InputMethodInfo, InputMethodSubtype, String)} for normal use cases. + *

+ * This method is to be used only for special cases where we knowingly want to set a + * particular keyboard layout for a keyboard, ignoring the IME configuration. e.g. Setting a + * default layout for an Android Emulator where we know the preferred H/W keyboard layout. + *

+ * NOTE: This may affect the typing experience if the layout isn't compatible with the IME + * configuration. + *

+ * NOTE: User can still change the keyboard layout configuration from the settings page. + *

+ * + * @param identifier The identifier for the input device. + * @param keyboardLayoutDescriptor The keyboard layout descriptor to use. + * + * @hide + */ + @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) + public void setKeyboardLayoutOverrideForInputDevice(@NonNull InputDeviceIdentifier identifier, + @NonNull String keyboardLayoutDescriptor) { + Objects.requireNonNull(identifier, "identifier should not be null"); + Objects.requireNonNull(keyboardLayoutDescriptor, + "keyboardLayoutDescriptor should not be null"); + try { + mIm.setKeyboardLayoutOverrideForInputDevice(identifier, keyboardLayoutDescriptor); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * TODO(b/330517633): Cleanup the unsupported API */ diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 2ba35d6a70d2..4e03e86dfe36 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1259,6 +1259,15 @@ public class InputManagerService extends IInputManager.Stub imeInfo, imeSubtype); } + @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT) + @Override // Binder call + public void setKeyboardLayoutOverrideForInputDevice(InputDeviceIdentifier identifier, + String keyboardLayoutDescriptor) { + super.setKeyboardLayoutOverrideForInputDevice_enforcePermission(); + mKeyboardLayoutManager.setKeyboardLayoutOverrideForInputDevice(identifier, + keyboardLayoutDescriptor); + } + @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT) @Override // Binder call public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 1d1a178ff20b..b8ce86b7c98c 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -113,6 +113,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { private static final int MSG_UPDATE_EXISTING_DEVICES = 1; private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 2; private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 3; + private static final String GLOBAL_OVERRIDE_KEY = "GLOBAL_OVERRIDE_KEY"; private final Context mContext; private final NativeInputManagerService mNative; @@ -507,27 +508,45 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { return result; } + @AnyThread + public void setKeyboardLayoutOverrideForInputDevice(InputDeviceIdentifier identifier, + String keyboardLayoutDescriptor) { + InputDevice inputDevice = getInputDevice(identifier); + if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { + return; + } + KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice); + setKeyboardLayoutForInputDeviceInternal(keyboardIdentifier, GLOBAL_OVERRIDE_KEY, + keyboardLayoutDescriptor); + } + @AnyThread public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) { - Objects.requireNonNull(keyboardLayoutDescriptor, - "keyboardLayoutDescriptor must not be null"); InputDevice inputDevice = getInputDevice(identifier); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { return; } KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice); - String layoutKey = new LayoutKey(keyboardIdentifier, + final String datastoreKey = new LayoutKey(keyboardIdentifier, new ImeInfo(userId, imeInfo, imeSubtype)).toString(); + setKeyboardLayoutForInputDeviceInternal(keyboardIdentifier, datastoreKey, + keyboardLayoutDescriptor); + } + + private void setKeyboardLayoutForInputDeviceInternal(KeyboardIdentifier identifier, + String datastoreKey, String keyboardLayoutDescriptor) { + Objects.requireNonNull(keyboardLayoutDescriptor, + "keyboardLayoutDescriptor must not be null"); synchronized (mDataStore) { try { - if (mDataStore.setKeyboardLayout(keyboardIdentifier.toString(), layoutKey, + if (mDataStore.setKeyboardLayout(identifier.toString(), datastoreKey, keyboardLayoutDescriptor)) { if (DEBUG) { Slog.d(TAG, "setKeyboardLayoutForInputDevice() " + identifier - + " key: " + layoutKey + + " key: " + datastoreKey + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor); } mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); @@ -635,6 +654,12 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { if (layout != null) { return new KeyboardLayoutSelectionResult(layout, LAYOUT_SELECTION_CRITERIA_USER); } + + layout = mDataStore.getKeyboardLayout(keyboardIdentifier.toString(), + GLOBAL_OVERRIDE_KEY); + if (layout != null) { + return new KeyboardLayoutSelectionResult(layout, LAYOUT_SELECTION_CRITERIA_DEVICE); + } } synchronized (mKeyboardLayoutCache) { diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index d6654cceb458..4440a839caef 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -28,6 +28,8 @@ import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal import android.hardware.input.KeyboardLayout import android.hardware.input.KeyboardLayoutSelectionResult +import android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE +import android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER import android.icu.util.ULocale import android.os.Bundle import android.os.test.TestLooper @@ -280,6 +282,41 @@ class KeyboardLayoutManagerTests { ) } + @Test + fun testGetSetKeyboardLayoutOverrideForInputDevice() { + val imeSubtype = createImeSubtype() + + keyboardLayoutManager.setKeyboardLayoutOverrideForInputDevice( + keyboardDevice.identifier, + ENGLISH_UK_LAYOUT_DESCRIPTOR + ) + var result = + keyboardLayoutManager.getKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype + ) + assertEquals(LAYOUT_SELECTION_CRITERIA_DEVICE, result.selectionCriteria) + assertEquals( + "getKeyboardLayoutForInputDevice API should return the set layout", + ENGLISH_UK_LAYOUT_DESCRIPTOR, + result.layoutDescriptor + ) + + // This should replace the overriding layout set above + keyboardLayoutManager.setKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype, + ENGLISH_US_LAYOUT_DESCRIPTOR + ) + result = keyboardLayoutManager.getKeyboardLayoutForInputDevice( + keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype + ) + assertEquals(LAYOUT_SELECTION_CRITERIA_USER, result.selectionCriteria) + assertEquals( + "getKeyboardLayoutForInputDevice API should return the user set layout", + ENGLISH_US_LAYOUT_DESCRIPTOR, + result.layoutDescriptor + ) + } + @Test fun testGetKeyboardLayoutListForInputDevice() { // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts -- cgit v1.2.3-59-g8ed1b