diff options
5 files changed, 130 insertions, 13 deletions
diff --git a/core/java/android/accessibilityservice/BrailleDisplayController.java b/core/java/android/accessibilityservice/BrailleDisplayController.java index 5282aa3fccf7..7334676c905d 100644 --- a/core/java/android/accessibilityservice/BrailleDisplayController.java +++ b/core/java/android/accessibilityservice/BrailleDisplayController.java @@ -305,4 +305,6 @@ public interface BrailleDisplayController { @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID) @TestApi String TEST_BRAILLE_DISPLAY_UNIQUE_ID = "UNIQUE_ID"; + /** @hide */ + String TEST_BRAILLE_DISPLAY_NAME = "NAME"; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index fb2805574ff0..1f65e15c1bff 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -37,6 +37,8 @@ import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -695,6 +697,13 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect throw new IllegalArgumentException( bluetoothAddress + " is not a valid Bluetooth address"); } + final BluetoothManager bluetoothManager = + mContext.getSystemService(BluetoothManager.class); + final String bluetoothDeviceName = bluetoothManager == null ? null : + bluetoothManager.getAdapter().getBondedDevices().stream() + .filter(device -> device.getAddress().equalsIgnoreCase(bluetoothAddress)) + .map(BluetoothDevice::getName) + .findFirst().orElse(null); synchronized (mLock) { checkAccessibilityAccessLocked(); if (mBrailleDisplayConnection != null) { @@ -706,7 +715,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect connection.setTestData(mTestBrailleDisplays); } connection.connectLocked( - bluetoothAddress, BrailleDisplayConnection.BUS_BLUETOOTH, controller); + bluetoothAddress, + bluetoothDeviceName, + BrailleDisplayConnection.BUS_BLUETOOTH, + controller); } } @@ -763,7 +775,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect connection.setTestData(mTestBrailleDisplays); } connection.connectLocked( - usbSerialNumber, BrailleDisplayConnection.BUS_USB, controller); + usbSerialNumber, + usbDevice.getProductName(), + BrailleDisplayConnection.BUS_USB, + controller); } } diff --git a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java index 8b41873636a9..b0da3f014452 100644 --- a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java @@ -24,6 +24,7 @@ import android.accessibilityservice.IBrailleDisplayConnection; import android.accessibilityservice.IBrailleDisplayController; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.PermissionManuallyEnforced; import android.annotation.RequiresNoPermission; import android.bluetooth.BluetoothDevice; @@ -33,6 +34,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; @@ -141,6 +143,8 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { @BusType int getDeviceBusType(@NonNull Path path); + + String getName(@NonNull Path path); } /** @@ -149,15 +153,19 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { * <p>If found, saves instance state for this connection and starts a thread to * read from the Braille display. * - * @param expectedUniqueId The expected unique ID of the device to connect, from - * {@link UsbDevice#getSerialNumber()} - * or {@link BluetoothDevice#getAddress()} - * @param expectedBusType The expected bus type from {@link BusType}. - * @param controller Interface containing oneway callbacks used to communicate with the - * {@link android.accessibilityservice.BrailleDisplayController}. + * @param expectedUniqueId The expected unique ID of the device to connect, from + * {@link UsbDevice#getSerialNumber()} or + * {@link BluetoothDevice#getAddress()}. + * @param expectedName The expected name of the device to connect, from + * {@link BluetoothDevice#getName()} or + * {@link UsbDevice#getProductName()}. + * @param expectedBusType The expected bus type from {@link BusType}. + * @param controller Interface containing oneway callbacks used to communicate with the + * {@link android.accessibilityservice.BrailleDisplayController}. */ void connectLocked( @NonNull String expectedUniqueId, + @Nullable String expectedName, @BusType int expectedBusType, @NonNull IBrailleDisplayController controller) { Objects.requireNonNull(expectedUniqueId); @@ -179,10 +187,20 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { unableToGetDescriptor = true; continue; } + final boolean matchesIdentifier; final String uniqueId = mScanner.getUniqueId(path); + if (uniqueId != null) { + matchesIdentifier = expectedUniqueId.equalsIgnoreCase(uniqueId); + } else { + // HIDIOCGRAWUNIQ was added in kernel version 5.7. + // If the device has an older kernel that does not support that ioctl then as a + // fallback we can check against the device name (from HIDIOCGRAWNAME). + final String name = mScanner.getName(path); + matchesIdentifier = !TextUtils.isEmpty(expectedName) && expectedName.equals(name); + } if (isBrailleDisplay(descriptor) && mScanner.getDeviceBusType(path) == expectedBusType - && expectedUniqueId.equalsIgnoreCase(uniqueId)) { + && matchesIdentifier) { result.add(Pair.create(path.toFile(), descriptor)); } } @@ -498,6 +516,12 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { Integer busType = readFromFileDescriptor(path, nativeInterface::getHidrawBusType); return busType != null ? busType : BUS_UNKNOWN; } + + @Override + public String getName(@NonNull Path path) { + Objects.requireNonNull(path); + return readFromFileDescriptor(path, nativeInterface::getHidrawName); + } }; } @@ -542,6 +566,12 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH) ? BUS_BLUETOOTH : BUS_USB; } + + @Override + public String getName(@NonNull Path path) { + return brailleDisplayMap.get(path).getString( + BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME); + } }; return mScanner; } @@ -579,6 +609,8 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { * @return the result of ioctl(HIDIOCGRAWINFO).bustype, or -1 if the ioctl fails. */ int getHidrawBusType(int fd); + + String getHidrawName(int fd); } /** Native interface that actually calls native HIDRAW ioctls. */ @@ -602,6 +634,11 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { public int getHidrawBusType(int fd) { return nativeGetHidrawBusType(fd); } + + @Override + public String getHidrawName(int fd) { + return nativeGetHidrawName(fd); + } } private static native int nativeGetHidrawDescSize(int fd); @@ -611,4 +648,6 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { private static native String nativeGetHidrawUniq(int fd); private static native int nativeGetHidrawBusType(int fd); + + private static native String nativeGetHidrawName(int fd); } diff --git a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp index c3375236098a..180081c9173a 100644 --- a/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp +++ b/services/core/jni/com_android_server_accessibility_BrailleDisplayConnection.cpp @@ -32,10 +32,10 @@ namespace android { namespace { -// Max size we allow for the result from HIDIOCGRAWUNIQ (Bluetooth address or USB serial number). -// Copied from linux/hid.h struct hid_device->uniq char array size; the ioctl implementation -// writes at most this many bytes to the provided buffer. -constexpr int UNIQ_SIZE_MAX = 64; +// Max sizes we allow for results from string ioctl calls, copied from UAPI linux/uhid.h. +// The ioctl implementation writes at most this many bytes to the provided buffer: +constexpr int NAME_SIZE_MAX = 128; // HIDIOCGRAWNAME (device name) +constexpr int UNIQ_SIZE_MAX = 64; // HIDIOCGRAWUNIQ (BT address or USB serial number) } // anonymous namespace @@ -82,6 +82,16 @@ static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawB return info.bustype; } +static jstring com_android_server_accessibility_BrailleDisplayConnection_getHidrawName( + JNIEnv* env, jclass /*clazz*/, int fd) { + char buf[NAME_SIZE_MAX]; + if (ioctl(fd, HIDIOCGRAWNAME(NAME_SIZE_MAX), buf) < 0) { + return nullptr; + } + // Local ref is not deleted because it is returned to Java + return env->NewStringUTF(buf); +} + static const JNINativeMethod gMethods[] = { {"nativeGetHidrawDescSize", "(I)I", (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawDescSize}, @@ -91,6 +101,8 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawUniq}, {"nativeGetHidrawBusType", "(I)I", (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawBusType}, + {"nativeGetHidrawName", "(I)Ljava/lang/String;", + (void*)com_android_server_accessibility_BrailleDisplayConnection_getHidrawName}, }; int register_com_android_server_accessibility_BrailleDisplayConnection(JNIEnv* env) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java index 344e2c21f0a5..69a98ace1c33 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.BrailleDisplayController; +import android.accessibilityservice.IBrailleDisplayController; import android.content.Context; import android.os.Bundle; import android.os.IBinder; @@ -174,6 +175,17 @@ public class BrailleDisplayConnectionTest { } @Test + public void defaultNativeScanner_getName_returnsName() { + String name = "My Braille Display"; + when(mNativeInterface.getHidrawName(anyInt())).thenReturn(name); + + BrailleDisplayConnection.BrailleDisplayScanner scanner = + BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); + + assertThat(scanner.getName(NULL_PATH)).isEqualTo(name); + } + + @Test public void write_bypassesServiceSideCheckWithLargeBuffer_disconnects() { Mockito.doNothing().when(mBrailleDisplayConnection).disconnect(); mBrailleDisplayConnection.write( @@ -201,6 +213,38 @@ public class BrailleDisplayConnectionTest { verify(mBrailleDisplayConnection).disconnect(); } + @Test + public void connect_unableToGetUniq_usesNameFallback() throws Exception { + try { + IBrailleDisplayController controller = + Mockito.mock(IBrailleDisplayController.class); + final Path path = Path.of("/dev/null"); + final String macAddress = "00:11:22:33:AA:BB"; + final String name = "My Braille Display"; + final byte[] descriptor = {0x05, 0x41}; + Bundle bd = new Bundle(); + bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH, + path.toString()); + bd.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, + descriptor); + bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name); + bd.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, true); + bd.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, null); + BrailleDisplayConnection.BrailleDisplayScanner scanner = + mBrailleDisplayConnection.setTestData(List.of(bd)); + // Validate that the test data is set up correctly before attempting connection: + assertThat(scanner.getUniqueId(path)).isNull(); + assertThat(scanner.getName(path)).isEqualTo(name); + + mBrailleDisplayConnection.connectLocked( + macAddress, name, BrailleDisplayConnection.BUS_BLUETOOTH, controller); + + verify(controller).onConnected(eq(mBrailleDisplayConnection), eq(descriptor)); + } finally { + mBrailleDisplayConnection.disconnect(); + } + } + // BrailleDisplayConnection#setTestData() is used to enable CTS testing with // test Braille display data, but its own implementation should also be tested // so that issues in this helper don't cause confusing failures in CTS. @@ -220,6 +264,9 @@ public class BrailleDisplayConnectionTest { String uniq1 = "uniq1", uniq2 = "uniq2"; bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq1); bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq2); + String name1 = "name1", name2 = "name2"; + bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name1); + bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME, name2); int bus1 = BrailleDisplayConnection.BUS_USB, bus2 = BrailleDisplayConnection.BUS_BLUETOOTH; bd1.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, @@ -235,6 +282,8 @@ public class BrailleDisplayConnectionTest { expect.that(scanner.getDeviceReportDescriptor(path2)).isEqualTo(desc2); expect.that(scanner.getUniqueId(path1)).isEqualTo(uniq1); expect.that(scanner.getUniqueId(path2)).isEqualTo(uniq2); + expect.that(scanner.getName(path1)).isEqualTo(name1); + expect.that(scanner.getName(path2)).isEqualTo(name2); expect.that(scanner.getDeviceBusType(path1)).isEqualTo(bus1); expect.that(scanner.getDeviceBusType(path2)).isEqualTo(bus2); } |