diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index e7f1916653021..295d69d4b27db 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -88,6 +88,7 @@ interface IVirtualDevice { IBinder token, in Point screenSize); void unregisterInputDevice(IBinder token); + int getInputDeviceId(IBinder token); boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event); boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event); boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event); diff --git a/core/java/android/hardware/input/VirtualInputDevice.java b/core/java/android/hardware/input/VirtualInputDevice.java index 2a79ef0e0afda..772ba8e36c5e9 100644 --- a/core/java/android/hardware/input/VirtualInputDevice.java +++ b/core/java/android/hardware/input/VirtualInputDevice.java @@ -49,6 +49,17 @@ abstract class VirtualInputDevice implements Closeable { mToken = token; } + /** + * @return The device id of this device. + * @hide + */ + public int getInputDeviceId() { + try { + return mVirtualDevice.getInputDeviceId(mToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } @Override @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index ec30369bd099e..02053cc7cfd3d 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -31,6 +31,7 @@ import android.hardware.input.VirtualMouseScrollEvent; import android.hardware.input.VirtualTouchEvent; import android.os.Handler; import android.os.IBinder; +import android.os.IInputConstants; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Slog; @@ -75,7 +76,7 @@ class InputController { @interface PhysType { } - private final Object mLock; + final Object mLock; /* Token -> file descriptor associations. */ @VisibleForTesting @@ -220,6 +221,19 @@ class InputController { } } + /** + * @return the device id for a given token (identifiying a device) + */ + int getInputDeviceId(IBinder token) { + synchronized (mLock) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(token); + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException("Could not get device id for given token"); + } + return inputDeviceDescriptor.getInputDeviceId(); + } + } + void setShowPointerIcon(boolean visible, int displayId) { mInputManagerInternal.setPointerIconVisible(visible, displayId); } @@ -393,10 +407,22 @@ class InputController { + inputDeviceDescriptor.getCreationOrderNumber()); fout.println(" type: " + inputDeviceDescriptor.getType()); fout.println(" phys: " + inputDeviceDescriptor.getPhys()); + fout.println( + " inputDeviceId: " + inputDeviceDescriptor.getInputDeviceId()); } } } + @VisibleForTesting + void addDeviceForTesting(IBinder deviceToken, int fd, int type, int displayId, + String phys, int inputDeviceId) { + synchronized (mLock) { + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, () -> {}, type, displayId, phys, + inputDeviceId)); + } + } + private static native int nativeOpenUinputDpad(String deviceName, int vendorId, int productId, String phys); private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId, @@ -493,16 +519,20 @@ class InputController { private final @Type int mType; private final int mDisplayId; private final String mPhys; + // The input device id that was associated to the device by the InputReader on device + // creation. + private final int mInputDeviceId; // Monotonically increasing number; devices with lower numbers were created earlier. private final long mCreationOrderNumber; InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type, - int displayId, String phys) { + int displayId, String phys, int inputDeviceId) { mFd = fd; mDeathRecipient = deathRecipient; mType = type; mDisplayId = displayId; mPhys = phys; + mInputDeviceId = inputDeviceId; mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement(); } @@ -533,6 +563,10 @@ class InputController { public String getPhys() { return mPhys; } + + public int getInputDeviceId() { + return mInputDeviceId; + } } private final class BinderDeathRecipient implements IBinder.DeathRecipient { @@ -558,6 +592,8 @@ class InputController { private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1); private final InputManager.InputDeviceListener mListener; + private int mInputDeviceId = IInputConstants.INVALID_INPUT_DEVICE_ID; + WaitForDevice(String deviceName, int vendorId, int productId) { mListener = new InputManager.InputDeviceListener() { @Override @@ -572,6 +608,7 @@ class InputController { if (id.getVendorId() != vendorId || id.getProductId() != productId) { return; } + mInputDeviceId = deviceId; mDeviceAddedLatch.countDown(); } @@ -588,8 +625,13 @@ class InputController { InputManager.getInstance().registerInputDeviceListener(mListener, mHandler); } - /** Note: This must not be called from {@link #mHandler}'s thread. */ - void waitForDeviceCreation() throws DeviceCreationException { + /** + * Note: This must not be called from {@link #mHandler}'s thread. + * @throws DeviceCreationException if the device was not created successfully within the + * timeout. + * @return The id of the created input device. + */ + int waitForDeviceCreation() throws DeviceCreationException { try { if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) { throw new DeviceCreationException( @@ -599,6 +641,12 @@ class InputController { throw new DeviceCreationException( "Interrupted while waiting for virtual device to be created.", e); } + if (mInputDeviceId == IInputConstants.INVALID_INPUT_DEVICE_ID) { + throw new IllegalStateException( + "Virtual input device was created with an invalid " + + "id=" + mInputDeviceId); + } + return mInputDeviceId; } @Override @@ -643,6 +691,8 @@ class InputController { final int fd; final BinderDeathRecipient binderDeathRecipient; + final int inputDeviceId; + setUniqueIdAssociation(displayId, phys); try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) { fd = deviceOpener.get(); @@ -652,7 +702,7 @@ class InputController { } // The fd is valid from here, so ensure that all failures close the fd after this point. try { - waiter.waitForDeviceCreation(); + inputDeviceId = waiter.waitForDeviceCreation(); binderDeathRecipient = new BinderDeathRecipient(deviceToken); try { @@ -672,7 +722,8 @@ class InputController { synchronized (mLock) { mInputDeviceDescriptors.put(deviceToken, - new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys)); + new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys, + inputDeviceId)); } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 2835b69b30397..5ebbf07526f1f 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -497,6 +497,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @Override // Binder call + public int getInputDeviceId(IBinder token) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.getInputDeviceId(token); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + + @Override // Binder call public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) { final long binderToken = Binder.clearCallingIdentity(); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java index 6b8c26d7b1d4e..d2f2af1b91b6f 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -16,6 +16,8 @@ package com.android.server.companion.virtual; +import static com.google.common.truth.Truth.assertWithMessage; + import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -25,6 +27,7 @@ import static org.mockito.Mockito.verify; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.IInputManager; +import android.hardware.input.InputManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -87,6 +90,30 @@ public class InputControllerTest { threadVerifier); } + @Test + public void registerInputDevice_deviceCreation_hasDeviceId() { + final IBinder device1Token = new Binder("device1"); + mInputController.createMouse("mouse", /*vendorId= */ 1, /*productId= */ 1, device1Token, + /* displayId= */ 1); + int device1Id = mInputController.getInputDeviceId(device1Token); + + final IBinder device2Token = new Binder("device2"); + mInputController.createKeyboard("keyboard", /*vendorId= */2, /*productId= */ 2, + device2Token, 2); + int device2Id = mInputController.getInputDeviceId(device2Token); + + assertWithMessage("Different devices should have different id").that( + device1Id).isNotEqualTo(device2Id); + + + int[] deviceIds = InputManager.getInstance().getInputDeviceIds(); + assertWithMessage("InputManager's deviceIds list should contain id of device 1").that( + deviceIds).asList().contains(device1Id); + assertWithMessage("InputManager's deviceIds list should contain id of device 2").that( + deviceIds).asList().contains(device2Id); + + } + @Test public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() { final IBinder deviceToken = new Binder(); @@ -115,4 +142,5 @@ public class InputControllerTest { mInputController.unregisterInputDevice(deviceToken); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); } + } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 9c5d1a5b06104..c5ba360b61705 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -121,6 +121,7 @@ public class VirtualDeviceManagerServiceTest { private static final int VENDOR_ID = 5; private static final String UNIQUE_ID = "uniqueid"; private static final String PHYS = "phys"; + private static final int DEVICE_ID = 42; private static final int HEIGHT = 1800; private static final int WIDTH = 900; private static final Binder BINDER = new Binder("binder"); @@ -529,6 +530,16 @@ public class VirtualDeviceManagerServiceTest { eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH)); } + @Test + public void createVirtualKeyboard_inputDeviceId_obtainFromInputController() { + final int fd = 1; + mInputController.addDeviceForTesting(BINDER, fd, /* type= */ 1, /* displayId= */ 1, PHYS, + DEVICE_ID); + assertWithMessage( + "InputController should return device id from InputDeviceDescriptor").that( + mInputController.getInputDeviceId(BINDER)).isEqualTo(DEVICE_ID); + } + @Test public void onAudioSessionStarting_hasVirtualAudioController() { mDeviceImpl.onVirtualDisplayCreatedLocked( @@ -578,7 +589,7 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualKeyEvent.ACTION_UP; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1, - /* displayId= */ 1, PHYS)); + /* displayId= */ 1, PHYS, DEVICE_ID)); mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode) .setAction(action).build()); verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action); @@ -603,7 +614,7 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS)); + /* displayId= */ 1, PHYS, DEVICE_ID)); doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() .setButtonCode(buttonCode) @@ -618,7 +629,7 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS)); + /* displayId= */ 1, PHYS, DEVICE_ID)); assertThrows( IllegalStateException.class, () -> @@ -644,7 +655,7 @@ public class VirtualDeviceManagerServiceTest { final float y = 0.7f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS)); + /* displayId= */ 1, PHYS, DEVICE_ID)); doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() .setRelativeX(x).setRelativeY(y).build()); @@ -658,7 +669,7 @@ public class VirtualDeviceManagerServiceTest { final float y = 0.7f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS)); + /* displayId= */ 1, PHYS, DEVICE_ID)); assertThrows( IllegalStateException.class, () -> @@ -685,7 +696,7 @@ public class VirtualDeviceManagerServiceTest { final float y = 1f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS)); + /* displayId= */ 1, PHYS, DEVICE_ID)); doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() .setXAxisMovement(x) @@ -700,7 +711,7 @@ public class VirtualDeviceManagerServiceTest { final float y = 1f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1, PHYS)); + /* displayId= */ 1, PHYS, DEVICE_ID)); assertThrows( IllegalStateException.class, () -> @@ -733,7 +744,7 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualTouchEvent.ACTION_UP; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, - /* displayId= */ 1, PHYS)); + /* displayId= */ 1, PHYS, DEVICE_ID)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build()); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN, @@ -752,7 +763,7 @@ public class VirtualDeviceManagerServiceTest { final float majorAxisSize = 10.0f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, - /* displayId= */ 1, PHYS)); + /* displayId= */ 1, PHYS, DEVICE_ID)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType) .setPressure(pressure).setMajorAxisSize(majorAxisSize).build());