diff options
11 files changed, 263 insertions, 264 deletions
| diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java index 7f2d8a026a2f..5985c39034ea 100644 --- a/core/java/android/hardware/input/VirtualDpad.java +++ b/core/java/android/hardware/input/VirtualDpad.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi;  import android.companion.virtual.IVirtualDevice;  import android.os.IBinder;  import android.os.RemoteException; +import android.util.Log;  import android.view.KeyEvent;  import java.util.Arrays; @@ -80,7 +81,10 @@ public class VirtualDpad extends VirtualInputDevice {                                  + event.getKeyCode()                                  + " sent to a VirtualDpad input device.");              } -            mVirtualDevice.sendDpadKeyEvent(mToken, event); +            if (!mVirtualDevice.sendDpadKeyEvent(mToken, event)) { +                Log.w(TAG, "Failed to send key event to virtual dpad " +                        + mConfig.getInputDeviceName()); +            }          } catch (RemoteException e) {              throw e.rethrowFromSystemServer();          } diff --git a/core/java/android/hardware/input/VirtualInputDevice.java b/core/java/android/hardware/input/VirtualInputDevice.java index 931e1ff10505..affa4ed7e983 100644 --- a/core/java/android/hardware/input/VirtualInputDevice.java +++ b/core/java/android/hardware/input/VirtualInputDevice.java @@ -20,6 +20,7 @@ import android.annotation.RequiresPermission;  import android.companion.virtual.IVirtualDevice;  import android.os.IBinder;  import android.os.RemoteException; +import android.util.Log;  import java.io.Closeable; @@ -32,6 +33,8 @@ import java.io.Closeable;   */  abstract class VirtualInputDevice implements Closeable { +    protected static final String TAG = "VirtualInputDevice"; +      /**       * The virtual device to which this VirtualInputDevice belongs to.       */ @@ -67,6 +70,7 @@ abstract class VirtualInputDevice implements Closeable {      @Override      @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)      public void close() { +        Log.d(TAG, "Closing virtual input device " + mConfig.getInputDeviceName());          try {              mVirtualDevice.unregisterInputDevice(mToken);          } catch (RemoteException e) { diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java index a8caa58ada01..a87980c34f2d 100644 --- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java +++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java @@ -19,6 +19,10 @@ package android.hardware.input;  import android.annotation.NonNull;  import android.annotation.SystemApi;  import android.os.Parcel; +import android.view.Display; + +import java.nio.charset.StandardCharsets; +import java.util.Objects;  /**   * Common configurations to create virtual input devices. @@ -27,6 +31,15 @@ import android.os.Parcel;   */  @SystemApi  public abstract class VirtualInputDeviceConfig { + +    /** +     * The maximum length of a device name (in bytes in UTF-8 encoding). +     * +     * This limitation comes directly from uinput. +     * See also UINPUT_MAX_NAME_SIZE in linux/uinput.h +     */ +    private static final int DEVICE_NAME_MAX_LENGTH = 80; +      /** The vendor id uniquely identifies the company who manufactured the device. */      private final int mVendorId;      /** @@ -44,18 +57,33 @@ public abstract class VirtualInputDeviceConfig {          mVendorId = builder.mVendorId;          mProductId = builder.mProductId;          mAssociatedDisplayId = builder.mAssociatedDisplayId; -        mInputDeviceName = builder.mInputDeviceName; +        mInputDeviceName = Objects.requireNonNull(builder.mInputDeviceName); + +        if (mAssociatedDisplayId == Display.INVALID_DISPLAY) { +            throw new IllegalArgumentException( +                    "Display association is required for virtual input devices."); +        } + +        // Comparison is greater or equal because the device name must fit into a const char* +        // including the \0-terminator. Therefore the actual number of bytes that can be used +        // for device name is DEVICE_NAME_MAX_LENGTH - 1 +        if (mInputDeviceName.getBytes(StandardCharsets.UTF_8).length >= DEVICE_NAME_MAX_LENGTH) { +            throw new IllegalArgumentException("Input device name exceeds maximum length of " +                    + DEVICE_NAME_MAX_LENGTH + "bytes: " + mInputDeviceName); +        }      }      protected VirtualInputDeviceConfig(@NonNull Parcel in) {          mVendorId = in.readInt();          mProductId = in.readInt();          mAssociatedDisplayId = in.readInt(); -        mInputDeviceName = in.readString8(); +        mInputDeviceName = Objects.requireNonNull(in.readString8());      }      /**       * The vendor id uniquely identifies the company who manufactured the device. +     * +     * @see Builder#setVendorId(int) (int)       */      public int getVendorId() {          return mVendorId; @@ -64,6 +92,8 @@ public abstract class VirtualInputDeviceConfig {      /**       * The product id uniquely identifies which product within the address space of a given vendor,       * identified by the device's vendor id. +     * +     * @see Builder#setProductId(int)       */      public int getProductId() {          return mProductId; @@ -71,6 +101,8 @@ public abstract class VirtualInputDeviceConfig {      /**       * The associated display ID of the virtual input device. +     * +     * @see Builder#setAssociatedDisplayId(int)       */      public int getAssociatedDisplayId() {          return mAssociatedDisplayId; @@ -78,6 +110,8 @@ public abstract class VirtualInputDeviceConfig {      /**       * The name of the virtual input device. +     * +     * @see Builder#setInputDeviceName(String)       */      @NonNull      public String getInputDeviceName() { @@ -117,11 +151,12 @@ public abstract class VirtualInputDeviceConfig {          private int mVendorId;          private int mProductId; -        private int mAssociatedDisplayId; -        @NonNull +        private int mAssociatedDisplayId = Display.INVALID_DISPLAY;          private String mInputDeviceName; -        /** @see VirtualInputDeviceConfig#getVendorId(). */ +        /** +         * Sets the vendor id of the device, identifying the company who manufactured the device. +         */          @NonNull          public T setVendorId(int vendorId) {              mVendorId = vendorId; @@ -129,24 +164,40 @@ public abstract class VirtualInputDeviceConfig {          } -        /** @see VirtualInputDeviceConfig#getProductId(). */ +        /** +         * Sets the product id of the device, uniquely identifying the device within the address +         * space of a given vendor, identified by the device's vendor id. +         */          @NonNull          public T setProductId(int productId) {              mProductId = productId;              return self();          } -        /** @see VirtualInputDeviceConfig#getAssociatedDisplayId(). */ +        /** +         * Sets the associated display ID of the virtual input device. Required. +         * +         * <p>The input device is restricted to the display with the given ID and may not send +         * events to any other display.</p> +         */          @NonNull          public T setAssociatedDisplayId(int displayId) {              mAssociatedDisplayId = displayId;              return self();          } -        /** @see VirtualInputDeviceConfig#getInputDeviceName(). */ +        /** +         * Sets the name of the virtual input device. Required. +         * +         * <p>The name must be unique among all input devices that belong to the same virtual +         * device.</p> +         * +         * <p>The maximum allowed length of the name is 80 bytes in UTF-8 encoding, enforced by +         * {@code UINPUT_MAX_NAME_SIZE}.</p> +         */          @NonNull          public T setInputDeviceName(@NonNull String deviceName) { -            mInputDeviceName = deviceName; +            mInputDeviceName = Objects.requireNonNull(deviceName);              return self();          } diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java index c90f8932a89d..6eb2ae38ed82 100644 --- a/core/java/android/hardware/input/VirtualKeyboard.java +++ b/core/java/android/hardware/input/VirtualKeyboard.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi;  import android.companion.virtual.IVirtualDevice;  import android.os.IBinder;  import android.os.RemoteException; +import android.util.Log;  import android.view.KeyEvent;  /** @@ -57,7 +58,10 @@ public class VirtualKeyboard extends VirtualInputDevice {                      "Unsupported key code " + event.getKeyCode()                          + " sent to a VirtualKeyboard input device.");              } -            mVirtualDevice.sendKeyEvent(mToken, event); +            if (!mVirtualDevice.sendKeyEvent(mToken, event)) { +                Log.w(TAG, "Failed to send key event to virtual keyboard " +                        + mConfig.getInputDeviceName()); +            }          } catch (RemoteException e) {              throw e.rethrowFromSystemServer();          } diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java index 51f3f69eb78e..fb0f70049273 100644 --- a/core/java/android/hardware/input/VirtualMouse.java +++ b/core/java/android/hardware/input/VirtualMouse.java @@ -23,6 +23,7 @@ import android.companion.virtual.IVirtualDevice;  import android.graphics.PointF;  import android.os.IBinder;  import android.os.RemoteException; +import android.util.Log;  import android.view.MotionEvent;  /** @@ -52,7 +53,10 @@ public class VirtualMouse extends VirtualInputDevice {      @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)      public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) {          try { -            mVirtualDevice.sendButtonEvent(mToken, event); +            if (!mVirtualDevice.sendButtonEvent(mToken, event)) { +                Log.w(TAG, "Failed to send button event to virtual mouse " +                        + mConfig.getInputDeviceName()); +            }          } catch (RemoteException e) {              throw e.rethrowFromSystemServer();          } @@ -69,7 +73,10 @@ public class VirtualMouse extends VirtualInputDevice {      @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)      public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) {          try { -            mVirtualDevice.sendScrollEvent(mToken, event); +            if (!mVirtualDevice.sendScrollEvent(mToken, event)) { +                Log.w(TAG, "Failed to send scroll event to virtual mouse " +                        + mConfig.getInputDeviceName()); +            }          } catch (RemoteException e) {              throw e.rethrowFromSystemServer();          } @@ -85,7 +92,10 @@ public class VirtualMouse extends VirtualInputDevice {      @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)      public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) {          try { -            mVirtualDevice.sendRelativeEvent(mToken, event); +            if (!mVirtualDevice.sendRelativeEvent(mToken, event)) { +                Log.w(TAG, "Failed to send relative event to virtual mouse " +                        + mConfig.getInputDeviceName()); +            }          } catch (RemoteException e) {              throw e.rethrowFromSystemServer();          } diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpad.java b/core/java/android/hardware/input/VirtualNavigationTouchpad.java index 61d72e2fd554..3dbb38568f68 100644 --- a/core/java/android/hardware/input/VirtualNavigationTouchpad.java +++ b/core/java/android/hardware/input/VirtualNavigationTouchpad.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi;  import android.companion.virtual.IVirtualDevice;  import android.os.IBinder;  import android.os.RemoteException; +import android.util.Log;  /**   * A virtual navigation touchpad representing a touch-based input mechanism on a remote device. @@ -53,7 +54,10 @@ public class VirtualNavigationTouchpad extends VirtualInputDevice {      @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)      public void sendTouchEvent(@NonNull VirtualTouchEvent event) {          try { -            mVirtualDevice.sendTouchEvent(mToken, event); +            if (!mVirtualDevice.sendTouchEvent(mToken, event)) { +                Log.w(TAG, "Failed to send touch event to virtual navigation touchpad " +                        + mConfig.getInputDeviceName()); +            }          } catch (RemoteException e) {              throw e.rethrowFromSystemServer();          } diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java index 4ac439e0eff1..2c800aadef37 100644 --- a/core/java/android/hardware/input/VirtualTouchscreen.java +++ b/core/java/android/hardware/input/VirtualTouchscreen.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi;  import android.companion.virtual.IVirtualDevice;  import android.os.IBinder;  import android.os.RemoteException; +import android.util.Log;  /**   * A virtual touchscreen representing a touch-based display input mechanism on a remote device. @@ -47,7 +48,10 @@ public class VirtualTouchscreen extends VirtualInputDevice {      @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)      public void sendTouchEvent(@NonNull VirtualTouchEvent event) {          try { -            mVirtualDevice.sendTouchEvent(mToken, event); +            if (!mVirtualDevice.sendTouchEvent(mToken, event)) { +                Log.w(TAG, "Failed to send touch event to virtual touchscreen " +                        + mConfig.getInputDeviceName()); +            }          } catch (RemoteException e) {              throw e.rethrowFromSystemServer();          } 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 eeaa423b1aef..74415b5a8a87 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -49,7 +49,6 @@ import com.android.server.input.InputManagerInternal;  import java.io.PrintWriter;  import java.lang.annotation.Retention;  import java.lang.annotation.RetentionPolicy; -import java.nio.charset.StandardCharsets;  import java.util.Iterator;  import java.util.Map;  import java.util.Objects; @@ -83,14 +82,6 @@ class InputController {      @interface PhysType {      } -    /** -     * The maximum length of a device name (in bytes in UTF-8 encoding). -     * -     * This limitation comes directly from uinput. -     * See also UINPUT_MAX_NAME_SIZE in linux/uinput.h -     */ -    private static final int DEVICE_NAME_MAX_LENGTH = 80; -      final Object mLock = new Object();      /* Token -> file descriptor associations. */ @@ -138,25 +129,17 @@ class InputController {          }      } -    void createDpad(@NonNull String deviceName, -                        int vendorId, -                        int productId, -                        @NonNull IBinder deviceToken, -                        int displayId) { +    void createDpad(@NonNull String deviceName, int vendorId, int productId, +            @NonNull IBinder deviceToken, int displayId) throws DeviceCreationException {          final String phys = createPhys(PHYS_TYPE_DPAD); -        try { -            createDeviceInternal(InputDeviceDescriptor.TYPE_DPAD, deviceName, vendorId, +        createDeviceInternal(InputDeviceDescriptor.TYPE_DPAD, deviceName, vendorId,                      productId, deviceToken, displayId, phys,                      () -> mNativeWrapper.openUinputDpad(deviceName, vendorId, productId, phys)); -        } catch (DeviceCreationException e) { -            throw new RuntimeException( -                    "Failed to create virtual dpad device '" + deviceName + "'.", e); -        }      }      void createKeyboard(@NonNull String deviceName, int vendorId, int productId,              @NonNull IBinder deviceToken, int displayId, @NonNull String languageTag, -            @NonNull String layoutType) { +            @NonNull String layoutType) throws DeviceCreationException {          final String phys = createPhys(PHYS_TYPE_KEYBOARD);          mInputManagerInternal.addKeyboardLayoutAssociation(phys, languageTag,                  layoutType); @@ -166,66 +149,42 @@ class InputController {                      () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));          } catch (DeviceCreationException e) {              mInputManagerInternal.removeKeyboardLayoutAssociation(phys); -            throw new RuntimeException( -                    "Failed to create virtual keyboard device '" + deviceName + "'.", e); +            throw e;          }      } -    void createMouse(@NonNull String deviceName, -            int vendorId, -            int productId, -            @NonNull IBinder deviceToken, -            int displayId) { +    void createMouse(@NonNull String deviceName, int vendorId, int productId, +            @NonNull IBinder deviceToken, int displayId) throws DeviceCreationException {          final String phys = createPhys(PHYS_TYPE_MOUSE); -        try { -            createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId, -                    deviceToken, displayId, phys, -                    () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys)); -        } catch (DeviceCreationException e) { -            throw new RuntimeException( -                    "Failed to create virtual mouse device: '" + deviceName + "'.", e); -        } +        createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId, +                deviceToken, displayId, phys, +                () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));          mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);      } -    void createTouchscreen(@NonNull String deviceName, -            int vendorId, -            int productId, -            @NonNull IBinder deviceToken, -            int displayId, -            int height, -            int width) { +    void createTouchscreen(@NonNull String deviceName, int vendorId, int productId, +            @NonNull IBinder deviceToken, int displayId, int height, int width) +            throws DeviceCreationException {          final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN); -        try { -            createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId, -                    productId, deviceToken, displayId, phys, -                    () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, -                            phys, height, width)); -        } catch (DeviceCreationException e) { -            throw new RuntimeException( -                    "Failed to create virtual touchscreen device '" + deviceName + "'.", e); -        } +        createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId, +                productId, deviceToken, displayId, phys, +                () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys, +                        height, width));      } -    void createNavigationTouchpad( -            @NonNull String deviceName, -            int vendorId, -            int productId, -            @NonNull IBinder deviceToken, -            int displayId, -            int touchpadHeight, -            int touchpadWidth) { +    void createNavigationTouchpad(@NonNull String deviceName, int vendorId, int productId, +            @NonNull IBinder deviceToken, int displayId, int height, int width) +            throws DeviceCreationException {          final String phys = createPhys(PHYS_TYPE_NAVIGATION_TOUCHPAD);          mInputManagerInternal.setTypeAssociation(phys, NAVIGATION_TOUCHPAD_DEVICE_TYPE);          try {              createDeviceInternal(InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD, deviceName,                      vendorId, productId, deviceToken, displayId, phys,                      () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, -                            phys, touchpadHeight, touchpadWidth)); +                            phys, height, width));          } catch (DeviceCreationException e) {              mInputManagerInternal.unsetTypeAssociation(phys); -            throw new RuntimeException( -                    "Failed to create virtual navigation touchpad device '" + deviceName + "'.", e); +            throw e;          }      } @@ -234,10 +193,10 @@ class InputController {              final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove(                      token);              if (inputDeviceDescriptor == null) { -                throw new IllegalArgumentException( -                        "Could not unregister input device for given token"); +                Slog.w(TAG, "Could not unregister input device for given token."); +            } else { +                closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor);              } -            closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor);          }      } @@ -326,21 +285,11 @@ class InputController {      }      /** -     * Validates a device name by checking length and whether a device with the same name -     * already exists. Throws exceptions if the validation fails. +     * Validates a device name by checking whether a device with the same name already exists.       * @param deviceName The name of the device to be validated       * @throws DeviceCreationException if {@code deviceName} is not valid.       */      private void validateDeviceName(String deviceName) throws DeviceCreationException { -        // Comparison is greater or equal because the device name must fit into a const char* -        // including the \0-terminator. Therefore the actual number of bytes that can be used -        // for device name is DEVICE_NAME_MAX_LENGTH - 1 -        if (deviceName.getBytes(StandardCharsets.UTF_8).length >= DEVICE_NAME_MAX_LENGTH) { -            throw new DeviceCreationException( -                    "Input device name exceeds maximum length of " + DEVICE_NAME_MAX_LENGTH -                            + "bytes: " + deviceName); -        } -          synchronized (mLock) {              for (int i = 0; i < mInputDeviceDescriptors.size(); ++i) {                  if (mInputDeviceDescriptors.valueAt(i).mName.equals(deviceName)) { @@ -365,8 +314,7 @@ class InputController {              final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(                      token);              if (inputDeviceDescriptor == null) { -                throw new IllegalArgumentException( -                        "Could not send key event to input device for given token"); +                return false;              }              return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getNativePointer(),                      event.getKeyCode(), event.getAction(), event.getEventTimeNanos()); @@ -378,8 +326,7 @@ class InputController {              final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(                      token);              if (inputDeviceDescriptor == null) { -                throw new IllegalArgumentException( -                        "Could not send key event to input device for given token"); +                return false;              }              return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getNativePointer(),                      event.getKeyCode(), event.getAction(), event.getEventTimeNanos()); @@ -391,8 +338,7 @@ class InputController {              final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(                      token);              if (inputDeviceDescriptor == null) { -                throw new IllegalArgumentException( -                        "Could not send button event to input device for given token"); +                return false;              }              if (inputDeviceDescriptor.getDisplayId()                      != mInputManagerInternal.getVirtualMousePointerDisplayId()) { @@ -409,8 +355,7 @@ class InputController {              final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(                      token);              if (inputDeviceDescriptor == null) { -                throw new IllegalArgumentException( -                        "Could not send touch event to input device for given token"); +                return false;              }              return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getNativePointer(),                      event.getPointerId(), event.getToolType(), event.getAction(), event.getX(), @@ -424,8 +369,7 @@ class InputController {              final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(                      token);              if (inputDeviceDescriptor == null) { -                throw new IllegalArgumentException( -                        "Could not send relative event to input device for given token"); +                return false;              }              if (inputDeviceDescriptor.getDisplayId()                      != mInputManagerInternal.getVirtualMousePointerDisplayId()) { @@ -442,8 +386,7 @@ class InputController {              final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(                      token);              if (inputDeviceDescriptor == null) { -                throw new IllegalArgumentException( -                        "Could not send scroll event to input device for given token"); +                return false;              }              if (inputDeviceDescriptor.getDisplayId()                      != mInputManagerInternal.getVirtualMousePointerDisplayId()) { @@ -758,13 +701,19 @@ class InputController {      }      /** An internal exception that is thrown to indicate an error when opening a virtual device. */ -    private static class DeviceCreationException extends Exception { +    static class DeviceCreationException extends Exception { +        DeviceCreationException() { +            super(); +        }          DeviceCreationException(String message) {              super(message);          } -        DeviceCreationException(String message, Exception cause) { +        DeviceCreationException(String message, Throwable cause) {              super(message, cause);          } +        DeviceCreationException(Throwable cause) { +            super(cause); +        }      }      /** 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 118943df1bf6..e6bfeb79fafb 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -694,6 +694,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub              mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(),                      config.getProductId(), deviceToken,                      getTargetDisplayIdForInput(config.getAssociatedDisplayId())); +        } catch (InputController.DeviceCreationException e) { +            throw new IllegalArgumentException(e);          } finally {              Binder.restoreCallingIdentity(ident);          } @@ -705,15 +707,17 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub          super.createVirtualKeyboard_enforcePermission();          Objects.requireNonNull(config);          checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId()); -        synchronized (mVirtualDeviceLock) { -            mLocaleList = LocaleList.forLanguageTags(config.getLanguageTag()); -        }          final long ident = Binder.clearCallingIdentity();          try {              mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(),                      config.getProductId(), deviceToken,                      getTargetDisplayIdForInput(config.getAssociatedDisplayId()),                      config.getLanguageTag(), config.getLayoutType()); +            synchronized (mVirtualDeviceLock) { +                mLocaleList = LocaleList.forLanguageTags(config.getLanguageTag()); +            } +        } catch (InputController.DeviceCreationException e) { +            throw new IllegalArgumentException(e);          } finally {              Binder.restoreCallingIdentity(ident);          } @@ -729,6 +733,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub          try {              mInputController.createMouse(config.getInputDeviceName(), config.getVendorId(),                      config.getProductId(), deviceToken, config.getAssociatedDisplayId()); +        } catch (InputController.DeviceCreationException e) { +            throw new IllegalArgumentException(e);          } finally {              Binder.restoreCallingIdentity(ident);          } @@ -741,19 +747,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub          super.createVirtualTouchscreen_enforcePermission();          Objects.requireNonNull(config);          checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId()); -        int screenHeight = config.getHeight(); -        int screenWidth = config.getWidth(); -        if (screenHeight <= 0 || screenWidth <= 0) { -            throw new IllegalArgumentException( -                    "Cannot create a virtual touchscreen, screen dimensions must be positive. Got: " -                            + "(" + screenWidth + ", " + screenHeight + ")"); -        } -          final long ident = Binder.clearCallingIdentity();          try {              mInputController.createTouchscreen(config.getInputDeviceName(), config.getVendorId(),                      config.getProductId(), deviceToken, config.getAssociatedDisplayId(), -                    screenHeight, screenWidth); +                    config.getHeight(), config.getWidth()); +        } catch (InputController.DeviceCreationException e) { +            throw new IllegalArgumentException(e);          } finally {              Binder.restoreCallingIdentity(ident);          } @@ -766,21 +766,15 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub          super.createVirtualNavigationTouchpad_enforcePermission();          Objects.requireNonNull(config);          checkVirtualInputDeviceDisplayIdAssociation(config.getAssociatedDisplayId()); -        int touchpadHeight = config.getHeight(); -        int touchpadWidth = config.getWidth(); -        if (touchpadHeight <= 0 || touchpadWidth <= 0) { -            throw new IllegalArgumentException( -                "Cannot create a virtual navigation touchpad, touchpad dimensions must be positive." -                    + " Got: (" + touchpadHeight + ", " + touchpadWidth + ")"); -        } -          final long ident = Binder.clearCallingIdentity();          try {              mInputController.createNavigationTouchpad(                      config.getInputDeviceName(), config.getVendorId(),                      config.getProductId(), deviceToken,                      getTargetDisplayIdForInput(config.getAssociatedDisplayId()), -                    touchpadHeight, touchpadWidth); +                    config.getHeight(), config.getWidth()); +        } catch (InputController.DeviceCreationException e) { +            throw new IllegalArgumentException(e);          } finally {              Binder.restoreCallingIdentity(ident);          } 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 7e6883bcec63..ccbbaa52ac21 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 @@ -100,7 +100,7 @@ public class InputControllerTest {      }      @Test -    public void registerInputDevice_deviceCreation_hasDeviceId() { +    public void registerInputDevice_deviceCreation_hasDeviceId() throws Exception {          final IBinder device1Token = new Binder("device1");          mInputController.createMouse("mouse", /*vendorId= */ 1, /*productId= */ 1, device1Token,                  /* displayId= */ 1); @@ -124,7 +124,7 @@ public class InputControllerTest {      }      @Test -    public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() { +    public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() throws Exception {          final IBinder deviceToken = new Binder();          mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,                  /* displayId= */ 1); @@ -137,7 +137,8 @@ public class InputControllerTest {      }      @Test -    public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride() { +    public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride() +            throws Exception {          final IBinder deviceToken = new Binder();          mInputController.createMouse("mouse1", /*vendorId= */ 1, /*productId= */ 1, deviceToken,                  /* displayId= */ 1); @@ -153,7 +154,7 @@ public class InputControllerTest {      }      @Test -    public void createNavigationTouchpad_hasDeviceId() { +    public void createNavigationTouchpad_hasDeviceId() throws Exception {          final IBinder deviceToken = new Binder();          mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,                  deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50); @@ -166,7 +167,7 @@ public class InputControllerTest {      }      @Test -    public void createNavigationTouchpad_setsTypeAssociation() { +    public void createNavigationTouchpad_setsTypeAssociation() throws Exception {          final IBinder deviceToken = new Binder();          mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,                  deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50); @@ -176,7 +177,7 @@ public class InputControllerTest {      }      @Test -    public void createAndUnregisterNavigationTouchpad_unsetsTypeAssociation() { +    public void createAndUnregisterNavigationTouchpad_unsetsTypeAssociation() throws Exception {          final IBinder deviceToken = new Binder();          mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,                  deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50); @@ -188,7 +189,7 @@ public class InputControllerTest {      }      @Test -    public void createKeyboard_addAndRemoveKeyboardLayoutAssociation() { +    public void createKeyboard_addAndRemoveKeyboardLayoutAssociation() throws Exception {          final IBinder deviceToken = new Binder("device");          mInputController.createKeyboard("keyboard", /*vendorId= */2, /*productId= */ 2, deviceToken, @@ -201,56 +202,7 @@ public class InputControllerTest {      }      @Test -    public void createInputDevice_tooLongNameRaisesException() { -        final IBinder deviceToken = new Binder("device"); -        // The underlying uinput implementation only supports device names up to 80 bytes. This -        // string is all ASCII characters, therefore if we have more than 80 ASCII characters we -        // will have more than 80 bytes. -        String deviceName = -                "This.is.a.very.long.device.name.that.exceeds.the.maximum.length.of.80.bytes" -                        + ".by.a.couple.bytes"; - -        assertThrows(RuntimeException.class, () -> { -            mInputController.createDpad(deviceName, /*vendorId= */3, /*productId=*/3, deviceToken, -                    1); -        }); -    } - -    @Test -    public void createInputDevice_tooLongDeviceNameRaisesException() { -        final IBinder deviceToken = new Binder("device"); -        // The underlying uinput implementation only supports device names up to 80 bytes (including -        // a 0-byte terminator). -        // This string is 79 characters and 80 bytes (including the 0-byte terminator) -        String deviceName = -                "This.is.a.very.long.device.name.that.exceeds.the.maximum.length01234567890123456"; - -        assertThrows(RuntimeException.class, () -> { -            mInputController.createDpad(deviceName, /*vendorId= */3, /*productId=*/3, deviceToken, -                    1); -        }); -    } - -    @Test -    public void createInputDevice_stringWithLessThanMaxCharsButMoreThanMaxBytesRaisesException() { -        final IBinder deviceToken = new Binder("device1"); - -        // Has only 39 characters but is 109 bytes as utf-8 -        String device_name = -                "░▄▄▄▄░\n" + -                "▀▀▄██►\n" + -                "▀▀███►\n" + -                "░▀███►░█►\n" + -                "▒▄████▀▀"; - -        assertThrows(RuntimeException.class, () -> { -            mInputController.createDpad(device_name, /*vendorId= */5, /*productId=*/5, -                    deviceToken, 1); -        }); -    } - -    @Test -    public void createInputDevice_duplicateNamesAreNotAllowed() { +    public void createInputDevice_duplicateNamesAreNotAllowed() throws Exception {          final IBinder deviceToken1 = new Binder("deviceToken1");          final IBinder deviceToken2 = new Binder("deviceToken2"); @@ -258,9 +210,9 @@ public class InputControllerTest {          mInputController.createDpad(sharedDeviceName, /*vendorId= */4, /*productId=*/4,                  deviceToken1, 1); -        assertThrows("Device names need to be unique", RuntimeException.class, () -> { -            mInputController.createDpad(sharedDeviceName, /*vendorId= */5, /*productId=*/5, -                    deviceToken2, 2); -        }); +        assertThrows("Device names need to be unique", +                InputController.DeviceCreationException.class, +                () -> mInputController.createDpad( +                        sharedDeviceName, /*vendorId= */5, /*productId=*/5, deviceToken2, 2));      }  } 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 30300ec3ad2e..0c857bc18d16 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 @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.any;  import static org.mockito.ArgumentMatchers.anyBoolean;  import static org.mockito.ArgumentMatchers.anyFloat;  import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong;  import static org.mockito.ArgumentMatchers.anyString;  import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.ArgumentMatchers.nullable; @@ -368,6 +369,18 @@ public class VirtualDeviceManagerServiceTest {                  new Handler(TestableLooper.get(this).getLooper()));          when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); +        when(mNativeWrapperMock.writeButtonEvent(anyLong(), anyInt(), anyInt(), anyLong())) +                .thenReturn(true); +        when(mNativeWrapperMock.writeRelativeEvent(anyLong(), anyFloat(), anyFloat(), anyLong())) +                .thenReturn(true); +        when(mNativeWrapperMock.writeScrollEvent(anyLong(), anyFloat(), anyFloat(), anyLong())) +                .thenReturn(true); +        when(mNativeWrapperMock.writeKeyEvent(anyLong(), anyInt(), anyInt(), anyLong())) +                .thenReturn(true); +        when(mNativeWrapperMock.writeTouchEvent(anyLong(), anyInt(), anyInt(), anyInt(), +                anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong())) +                .thenReturn(true); +          mInputManagerMockHelper = new InputManagerMockHelper(                  TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);          // Allow virtual devices to be created on the looper thread for testing. @@ -1183,12 +1196,12 @@ public class VirtualDeviceManagerServiceTest {      @Test      public void sendKeyEvent_noFd() { -        assertThrows( -                IllegalArgumentException.class, -                () -> -                        mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder() -                                .setKeyCode(KeyEvent.KEYCODE_A) -                                .setAction(VirtualKeyEvent.ACTION_DOWN).build())); +        assertThat(mDeviceImpl.sendKeyEvent(BINDER, +                new VirtualKeyEvent.Builder() +                        .setKeyCode(KeyEvent.KEYCODE_A) +                        .setAction(VirtualKeyEvent.ACTION_DOWN) +                        .build())) +                .isFalse();      }      @Test @@ -1201,24 +1214,24 @@ public class VirtualDeviceManagerServiceTest {                  InputController.InputDeviceDescriptor.TYPE_KEYBOARD, DISPLAY_ID_1, PHYS,                  DEVICE_NAME_1, INPUT_DEVICE_ID); -        mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder() -                .setKeyCode(keyCode) -                .setAction(action) -                .setEventTimeNanos(eventTimeNanos) -                .build()); +        assertThat(mDeviceImpl.sendKeyEvent(BINDER, +                new VirtualKeyEvent.Builder() +                        .setKeyCode(keyCode) +                        .setAction(action) +                        .setEventTimeNanos(eventTimeNanos) +                        .build())) +                .isTrue();          verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action, eventTimeNanos);      }      @Test      public void sendButtonEvent_noFd() { -        assertThrows( -                IllegalArgumentException.class, -                () -> -                        mDeviceImpl.sendButtonEvent(BINDER, -                                new VirtualMouseButtonEvent.Builder() -                                        .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK) -                                        .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS) -                                        .build())); +        assertThat(mDeviceImpl.sendButtonEvent(BINDER, +                new VirtualMouseButtonEvent.Builder() +                        .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK) +                        .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS) +                        .build())) +                .isFalse();      }      @Test @@ -1231,11 +1244,13 @@ public class VirtualDeviceManagerServiceTest {                  InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS,                  DEVICE_NAME_1, INPUT_DEVICE_ID);          doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); -        mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() -                .setButtonCode(buttonCode) -                .setAction(action) -                .setEventTimeNanos(eventTimeNanos) -                .build()); +        assertThat(mDeviceImpl.sendButtonEvent(BINDER, +                new VirtualMouseButtonEvent.Builder() +                        .setButtonCode(buttonCode) +                        .setAction(action) +                        .setEventTimeNanos(eventTimeNanos) +                        .build())) +                .isTrue();          verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action, eventTimeNanos);      } @@ -1257,12 +1272,12 @@ public class VirtualDeviceManagerServiceTest {      @Test      public void sendRelativeEvent_noFd() { -        assertThrows( -                IllegalArgumentException.class, -                () -> -                        mDeviceImpl.sendRelativeEvent(BINDER, -                                new VirtualMouseRelativeEvent.Builder().setRelativeX( -                                        0.0f).setRelativeY(0.0f).build())); +        assertThat(mDeviceImpl.sendRelativeEvent(BINDER, +                new VirtualMouseRelativeEvent.Builder() +                        .setRelativeX(0.0f) +                        .setRelativeY(0.0f) +                        .build())) +                .isFalse();      }      @Test @@ -1275,14 +1290,17 @@ public class VirtualDeviceManagerServiceTest {                  InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,                  INPUT_DEVICE_ID);          doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); -        mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() -                .setRelativeX(x) -                .setRelativeY(y) -                .setEventTimeNanos(eventTimeNanos) -                .build()); +        assertThat(mDeviceImpl.sendRelativeEvent(BINDER, +                new VirtualMouseRelativeEvent.Builder() +                        .setRelativeX(x) +                        .setRelativeY(y) +                        .setEventTimeNanos(eventTimeNanos) +                        .build())) +                .isTrue();          verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y, eventTimeNanos);      } +      @Test      public void sendRelativeEvent_hasFd_wrongDisplay_throwsIllegalStateException() {          final int fd = 1; @@ -1301,13 +1319,12 @@ public class VirtualDeviceManagerServiceTest {      @Test      public void sendScrollEvent_noFd() { -        assertThrows( -                IllegalArgumentException.class, -                () -> -                        mDeviceImpl.sendScrollEvent(BINDER, -                                new VirtualMouseScrollEvent.Builder() -                                        .setXAxisMovement(-1f) -                                        .setYAxisMovement(1f).build())); +        assertThat(mDeviceImpl.sendScrollEvent(BINDER, +                new VirtualMouseScrollEvent.Builder() +                        .setXAxisMovement(-1f) +                        .setYAxisMovement(1f) +                        .build())) +                .isFalse();      }      @Test @@ -1320,14 +1337,17 @@ public class VirtualDeviceManagerServiceTest {                  InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,                  INPUT_DEVICE_ID);          doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); -        mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() -                .setXAxisMovement(x) -                .setYAxisMovement(y) -                .setEventTimeNanos(eventTimeNanos) -                .build()); +        assertThat(mDeviceImpl.sendScrollEvent(BINDER, +                new VirtualMouseScrollEvent.Builder() +                        .setXAxisMovement(x) +                        .setYAxisMovement(y) +                        .setEventTimeNanos(eventTimeNanos) +                        .build())) +                .isTrue();          verify(mNativeWrapperMock).writeScrollEvent(fd, x, y, eventTimeNanos);      } +      @Test      public void sendScrollEvent_hasFd_wrongDisplay_throwsIllegalStateException() {          final int fd = 1; @@ -1346,16 +1366,15 @@ public class VirtualDeviceManagerServiceTest {      @Test      public void sendTouchEvent_noFd() { -        assertThrows( -                IllegalArgumentException.class, -                () -> -                        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder() -                                .setX(0.0f) -                                .setY(0.0f) -                                .setAction(VirtualTouchEvent.ACTION_UP) -                                .setPointerId(1) -                                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) -                                .build())); +        assertThat(mDeviceImpl.sendTouchEvent(BINDER, +                new VirtualTouchEvent.Builder() +                        .setX(0.0f) +                        .setY(0.0f) +                        .setAction(VirtualTouchEvent.ACTION_UP) +                        .setPointerId(1) +                        .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER) +                        .build())) +                .isFalse();      }      @Test @@ -1370,14 +1389,16 @@ public class VirtualDeviceManagerServiceTest {          mInputController.addDeviceForTesting(BINDER, fd,                  InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS,                  DEVICE_NAME_1, INPUT_DEVICE_ID); -        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder() -                .setX(x) -                .setY(y) -                .setAction(action) -                .setPointerId(pointerId) -                .setToolType(toolType) -                .setEventTimeNanos(eventTimeNanos) -                .build()); +        assertThat(mDeviceImpl.sendTouchEvent(BINDER, +                new VirtualTouchEvent.Builder() +                        .setX(x) +                        .setY(y) +                        .setAction(action) +                        .setPointerId(pointerId) +                        .setToolType(toolType) +                        .setEventTimeNanos(eventTimeNanos) +                        .build())) +                .isTrue();          verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,                  Float.NaN, eventTimeNanos);      } @@ -1396,16 +1417,18 @@ public class VirtualDeviceManagerServiceTest {          mInputController.addDeviceForTesting(BINDER, fd,                  InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS,                  DEVICE_NAME_1, INPUT_DEVICE_ID); -        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder() -                .setX(x) -                .setY(y) -                .setAction(action) -                .setPointerId(pointerId) -                .setToolType(toolType) -                .setPressure(pressure) -                .setMajorAxisSize(majorAxisSize) -                .setEventTimeNanos(eventTimeNanos) -                .build()); +        assertThat(mDeviceImpl.sendTouchEvent(BINDER, +                new VirtualTouchEvent.Builder() +                        .setX(x) +                        .setY(y) +                        .setAction(action) +                        .setPointerId(pointerId) +                        .setToolType(toolType) +                        .setPressure(pressure) +                        .setMajorAxisSize(majorAxisSize) +                        .setEventTimeNanos(eventTimeNanos) +                        .build())) +                .isTrue();          verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure,                  majorAxisSize, eventTimeNanos);      } |