Virtual input API improvements

 - Do not crash when closing a closed input device
 - Do crash when the input device failed to be created
 - Log the device name when an event failed to be sent
 - Log the device name when closing the device
 - Remove redundant checks already covered in the configs
 - Add missing checks in the configs
 - Add missing API documentation

Fix: 306679262
Fix: 268457357
Test: CTS

Change-Id: Icbe521a7ba46c460d3b98286b2c4c05ce6e8e2c6
diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java
index 7f2d8a0..5985c39 100644
--- a/core/java/android/hardware/input/VirtualDpad.java
+++ b/core/java/android/hardware/input/VirtualDpad.java
@@ -22,6 +22,7 @@
 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 @@
                                 + 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 931e1ff..affa4ed 100644
--- a/core/java/android/hardware/input/VirtualInputDevice.java
+++ b/core/java/android/hardware/input/VirtualInputDevice.java
@@ -20,6 +20,7 @@
 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 @@
  */
 abstract class VirtualInputDevice implements Closeable {
 
+    protected static final String TAG = "VirtualInputDevice";
+
     /**
      * The virtual device to which this VirtualInputDevice belongs to.
      */
@@ -67,6 +70,7 @@
     @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 a8caa58..a87980c 100644
--- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java
+++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
@@ -19,6 +19,10 @@
 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 @@
  */
 @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 @@
         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 @@
     /**
      * 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 @@
 
     /**
      * The associated display ID of the virtual input device.
+     *
+     * @see Builder#setAssociatedDisplayId(int)
      */
     public int getAssociatedDisplayId() {
         return mAssociatedDisplayId;
@@ -78,6 +110,8 @@
 
     /**
      * The name of the virtual input device.
+     *
+     * @see Builder#setInputDeviceName(String)
      */
     @NonNull
     public String getInputDeviceName() {
@@ -117,11 +151,12 @@
 
         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 @@
         }
 
 
-        /** @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 c90f893..6eb2ae3 100644
--- a/core/java/android/hardware/input/VirtualKeyboard.java
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -22,6 +22,7 @@
 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 @@
                     "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 51f3f69..fb0f700 100644
--- a/core/java/android/hardware/input/VirtualMouse.java
+++ b/core/java/android/hardware/input/VirtualMouse.java
@@ -23,6 +23,7 @@
 import android.graphics.PointF;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 import android.view.MotionEvent;
 
 /**
@@ -52,7 +53,10 @@
     @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 @@
     @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 @@
     @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 61d72e2..3dbb385 100644
--- a/core/java/android/hardware/input/VirtualNavigationTouchpad.java
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
@@ -22,6 +22,7 @@
 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 @@
     @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 4ac439e..2c800aa 100644
--- a/core/java/android/hardware/input/VirtualTouchscreen.java
+++ b/core/java/android/hardware/input/VirtualTouchscreen.java
@@ -22,6 +22,7 @@
 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 @@
     @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 eeaa423..74415b5 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 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 @@
     @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 @@
         }
     }
 
-    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 @@
                     () -> 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 @@
             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 @@
     }
 
     /**
-     * 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 @@
             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 @@
             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 @@
             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 @@
             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 @@
             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 @@
             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 @@
     }
 
     /** 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 118943d..e6bfeb7 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 @@
             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 @@
         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 @@
         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 @@
         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 @@
         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 7e6883b..ccbbaa5 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 @@
     }
 
     @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 @@
     }
 
     @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 @@
     }
 
     @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 @@
     }
 
     @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 @@
     }
 
     @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 @@
     }
 
     @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 @@
     }
 
     @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 @@
     }
 
     @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 @@
 
         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 30300ec..0c857bc1 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.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 @@
                 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 @@
 
     @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 @@
                 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 @@
                 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 @@
 
     @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 @@
                 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 @@
 
     @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 @@
                 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 @@
 
     @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 @@
         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 @@
         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);
     }