Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5604d48b authored by Vaibhav Devmurari's avatar Vaibhav Devmurari
Browse files

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
parent c935347d
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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)")
+39 −0
Original line number Diff line number Diff line
@@ -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.
     *
     * <p>
     * Prefer using {@link InputManager#setKeyboardLayoutForInputDevice(InputDeviceIdentifier, int,
     * InputMethodInfo, InputMethodSubtype, String)} for normal use cases.
     * </p><p>
     * 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.
     * </p><p>
     * NOTE: This may affect the typing experience if the layout isn't compatible with the IME
     * configuration.
     * </p><p>
     * NOTE: User can still change the keyboard layout configuration from the settings page.
     * </p>
     *
     * @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
     */
+9 −0
Original line number Diff line number Diff line
@@ -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,
+30 −5
Original line number Diff line number Diff line
@@ -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) {
+37 −0
Original line number Diff line number Diff line
@@ -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