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

Commit c2099b12 authored by Jimmy's avatar Jimmy
Browse files

Add createVirtualKeyboard API

Adds a new system API guarded to allow creating a virtual keyboard.
Access to API is guarded by the privileged permission:
INJECT_KEY_EVENTS.

Design Doc: go/a11y-al-pk-dd

Bug: 397497164
Bug: 402581709

Test: Local tests with separate IME
Flag: com.android.hardware.input.create_virtual_keyboard_api

Change-Id: I9e6de531665967b3e5c34c4fe5d8e5ad2df81bd8
parent 307b1b2b
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -6084,6 +6084,10 @@ package android.hardware.hdmi {
package android.hardware.input {
  public final class InputManager {
    method @FlaggedApi("com.android.hardware.input.create_virtual_keyboard_api") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.INJECT_KEY_EVENTS, android.Manifest.permission.INJECT_EVENTS}) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.input.VirtualKeyboardConfig);
  }
  public class VirtualDpad implements java.io.Closeable {
    method public void close();
    method @FlaggedApi("com.android.hardware.input.create_virtual_keyboard_api") public int getInputDeviceId();
+6 −0
Original line number Diff line number Diff line
@@ -31,8 +31,10 @@ import android.hardware.input.IKeyGestureEventListener;
import android.hardware.input.IKeyGestureHandler;
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.IVirtualInputDevice;
import android.hardware.input.KeyboardLayoutSelectionResult;
import android.hardware.input.TouchCalibration;
import android.hardware.input.VirtualKeyboardConfig;
import android.os.CombinedVibration;
import android.hardware.input.IInputSensorEventListener;
import android.hardware.input.IKeyEventActivityListener;
@@ -84,6 +86,10 @@ interface IInputManager {
    @UnsupportedAppUsage
    boolean injectInputEvent(in InputEvent ev, int mode);

    @EnforcePermission(anyOf = {"INJECT_KEY_EVENTS", "INJECT_EVENTS"})
    IVirtualInputDevice createVirtualKeyboard(in IBinder token,
            in VirtualKeyboardConfig config);

    // Injects an input event into the system. The caller must have the INJECT_EVENTS permission.
    // The caller can target windows owned by a certain UID by providing a valid UID, or by
    // providing {@link android.os.Process#INVALID_UID} to target all windows.
+22 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UserHandleAware;
@@ -1000,6 +1001,27 @@ public final class InputManager {
        return mGlobal.injectInputEvent(event, mode, targetUid);
    }

    /**
     * Returns a {@link VirtualKeyboard} to the caller.
     * See {@link android.hardware.input.VirtualKeyboardConfig} for additional configurations
     * available, e.g. display association, layout, and language.
     *
     * @param config the keyboard configuration
     * @return VirtualKeyboard a virtual keyboard device
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(anyOf = {
            Manifest.permission.INJECT_KEY_EVENTS,
            Manifest.permission.INJECT_EVENTS
    })
    @FlaggedApi(com.android.hardware.input.Flags.FLAG_CREATE_VIRTUAL_KEYBOARD_API)
    @NonNull
    public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
        return mGlobal.createVirtualKeyboard(config);
    }

    /**
     * Injects an input event into the event system on behalf of an application.
     * The synchronization mode determines whether the method blocks while waiting for
+22 −0
Original line number Diff line number Diff line
@@ -1715,6 +1715,28 @@ public final class InputManagerGlobal {
        return injectInputEvent(event, mode, Process.INVALID_UID);
    }

    /**
     * @see InputManager#createVirtualKeyboard(VirtualKeyboardConfig)
     */
    @NonNull
    @RequiresPermission(anyOf = {
            Manifest.permission.INJECT_KEY_EVENTS,
            Manifest.permission.INJECT_EVENTS
    })
    public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
        IVirtualInputDevice virtualInputDevice;
        try {
            // Pass a token to the server so that the server can be notified when the calling
            // process has died and therefore clean up the virtual device.
            final IBinder token = new Binder(
                    "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
            virtualInputDevice = mIm.createVirtualKeyboard(token, config);
            return new VirtualKeyboard(config, virtualInputDevice);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * @see InputManager#setPointerIcon(PointerIcon, int, int, int, IBinder)
     */
+56 −18
Original line number Diff line number Diff line
@@ -114,6 +114,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.IInputFilter;
import android.view.IInputFilterHost;
import android.view.IInputMonitorHost;
@@ -1058,6 +1059,33 @@ public class InputManagerService extends IInputManager.Stub
        }
    }

    @NonNull
    @Override
    @EnforcePermission(anyOf = {
            Manifest.permission.INJECT_KEY_EVENTS,
            Manifest.permission.INJECT_EVENTS
    })
    public IVirtualInputDevice createVirtualKeyboard(@NonNull IBinder token,
            @NonNull VirtualKeyboardConfig config) {
        super.createVirtualKeyboard_enforcePermission();

        int displayId = config.getAssociatedDisplayId();
        if (displayId != Display.INVALID_DISPLAY && displayId != Display.DEFAULT_DISPLAY) {
            DisplayInfo displayInfo =
                    mDisplayManagerInternal.getDisplayInfo(displayId);
            int callingUid = Binder.getCallingUid();
            // Explicit display association requires either the caller to own the display or if
            // it's from the system.
            if (callingUid != displayInfo.ownerUid && callingUid != Process.SYSTEM_UID
                    && callingUid != 0) {
                throw new SecurityException(
                        "Explicit display association requires caller to own the display");
            }
        }

        return createVirtualKeyboardInternal(token, config);
    }

    @Override // Binder call
    public VerifiedInputEvent verifyInputEvent(@NonNull InputEvent event) {
        Objects.requireNonNull(event, "event must not be null");
@@ -1468,6 +1496,17 @@ public class InputManagerService extends IInputManager.Stub
        mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible);
    }

    // For display mirroring, we want to dispatch all key events to the source (default)
    // display, as the virtual display doesn't have any focused windows. Hence, call this for
    // associating any input device to the source display if the input device emits any key
    // events.
    private int getTargetDisplayIdForInput(int displayId) {
        DisplayManagerInternal displayManager = LocalServices.getService(
                DisplayManagerInternal.class);
        int mirroredDisplayId = displayManager.getDisplayIdToMirror(displayId);
        return mirroredDisplayId == Display.INVALID_DISPLAY ? displayId : mirroredDisplayId;
    }

    private static class VibrationInfo {
        private final long[] mPattern;
        private final int[] mAmplitudes;
@@ -1930,6 +1969,16 @@ public class InputManagerService extends IInputManager.Stub
        mNative.changeVirtualDevices();
    }

    @NonNull
    IVirtualInputDevice createVirtualKeyboardInternal(@NonNull IBinder token,
            @NonNull VirtualKeyboardConfig config) {
        return mVirtualInputDeviceController.createKeyboard(config.getInputDeviceName(),
                config.getVendorId(), config.getProductId(), token,
                InputManagerService.this.getTargetDisplayIdForInput(
                        config.getAssociatedDisplayId()),
                config.getLanguageTag(), config.getLayoutType());
    }

    @Override // Binder call
    public InputSensorInfo[] getSensorList(int deviceId) {
        return mNative.getSensorList(deviceId);
@@ -3873,10 +3922,7 @@ public class InputManagerService extends IInputManager.Stub
        @Override
        public IVirtualInputDevice createVirtualKeyboard(@NonNull IBinder token,
                @NonNull VirtualKeyboardConfig config) {
            return mVirtualInputDeviceController.createKeyboard(config.getInputDeviceName(),
                    config.getVendorId(), config.getProductId(), token,
                    getTargetDisplayIdForInput(config.getAssociatedDisplayId()),
                    config.getLanguageTag(), config.getLayoutType());
            return InputManagerService.this.createVirtualKeyboardInternal(token, config);
        }

        @NonNull
@@ -3904,7 +3950,8 @@ public class InputManagerService extends IInputManager.Stub
            return mVirtualInputDeviceController.createNavigationTouchpad(
                    config.getInputDeviceName(), config.getVendorId(),
                    config.getProductId(), token,
                    getTargetDisplayIdForInput(config.getAssociatedDisplayId()),
                    InputManagerService.this.getTargetDisplayIdForInput(
                            config.getAssociatedDisplayId()),
                    config.getHeight(), config.getWidth());
        }

@@ -3914,7 +3961,8 @@ public class InputManagerService extends IInputManager.Stub
                @NonNull VirtualDpadConfig config) {
            return mVirtualInputDeviceController.createDpad(config.getInputDeviceName(),
                    config.getVendorId(), config.getProductId(), token,
                    getTargetDisplayIdForInput(config.getAssociatedDisplayId()));
                    InputManagerService.this.getTargetDisplayIdForInput(
                            config.getAssociatedDisplayId()));
        }

        @NonNull
@@ -3933,24 +3981,14 @@ public class InputManagerService extends IInputManager.Stub
                @NonNull VirtualRotaryEncoderConfig config) {
            return mVirtualInputDeviceController.createRotaryEncoder(config.getInputDeviceName(),
                    config.getVendorId(), config.getProductId(), token,
                    getTargetDisplayIdForInput(config.getAssociatedDisplayId()));
                    InputManagerService.this.getTargetDisplayIdForInput(
                            config.getAssociatedDisplayId()));
        }

        @Override
        public void closeVirtualInputDevice(IBinder token) {
            mVirtualInputDeviceController.unregisterInputDevice(token);
        }

        // For display mirroring, we want to dispatch all key events to the source (default)
        // display, as the virtual display doesn't have any focused windows. Hence, call this for
        // associating any input device to the source display if the input device emits any key
        // events.
        private int getTargetDisplayIdForInput(int displayId) {
            DisplayManagerInternal displayManager = LocalServices.getService(
                    DisplayManagerInternal.class);
            int mirroredDisplayId = displayManager.getDisplayIdToMirror(displayId);
            return mirroredDisplayId == Display.INVALID_DISPLAY ? displayId : mirroredDisplayId;
        }
    }

    @Override