diff options
2 files changed, 292 insertions, 149 deletions
| diff --git a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java index 9b27dd347caf..40b6ff01965e 100644 --- a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java @@ -232,9 +232,63 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {      }      /** Returns true if this descriptor includes usages for the Braille display usage page 0x41. */ -    private static boolean isBrailleDisplay(byte[] descriptor) { -        // TODO: b/316036493 - Check that descriptor includes 0x41 reports. -        return true; +    @VisibleForTesting +    static boolean isBrailleDisplay(byte[] descriptor) { +        boolean foundMatch = false; +        for (int i = 0; i < descriptor.length; i++) { +            // HID Spec "6.2.2.2 Short Items" defines that the report descriptor is a collection of +            // items: each item is a collection of bytes where the first byte defines info about +            // the type of item and the following 0, 1, 2, or 4 bytes are data bytes for that item. +            // All items in the HID descriptor are expected to be Short Items. +            final byte itemInfo = descriptor[i]; +            if (!isHidItemShort(itemInfo)) { +                Slog.w(LOG_TAG, "Item " + itemInfo + " declares unsupported long type"); +                return false; +            } +            final int dataSize = getHidItemDataSize(itemInfo); +            if (i + dataSize >= descriptor.length) { +                Slog.w(LOG_TAG, "Item " + itemInfo + " specifies size past the remaining bytes"); +                return false; +            } +            // The item we're looking for (usage page declaration) should have size 1. +            if (dataSize == 1) { +                final byte itemData = descriptor[i + 1]; +                if (isHidItemBrailleDisplayUsagePage(itemInfo, itemData)) { +                    foundMatch = true; +                } +            } +            // Move to the next item by skipping past all data bytes in this item. +            i += dataSize; +        } +        return foundMatch; +    } + +    private static boolean isHidItemShort(byte itemInfo) { +        // Info bits 7-4 describe the item type, and HID Spec "6.2.2.3 Long Items" says that long +        // items always have type bits 1111. Otherwise, the item is a short item. +        return (itemInfo & 0b1111_0000) != 0b1111_0000; +    } + +    private static int getHidItemDataSize(byte itemInfo) { +        // HID Spec "6.2.2.2 Short Items" says that info bits 0-1 specify the optional data size: +        // 0, 1, 2, or 4 bytes. +        return switch (itemInfo & 0b0000_0011) { +            case 0b00 -> 0; +            case 0b01 -> 1; +            case 0b10 -> 2; +            default -> 4; +        }; +    } + +    private static boolean isHidItemBrailleDisplayUsagePage(byte itemInfo, byte itemData) { +        // From HID Spec "6.2.2.7 Global Items" +        final byte usagePageType = 0b0000_0100; +        // From HID Usage Tables version 1.2. +        final byte brailleDisplayUsagePage = 0x41; +        // HID Spec "6.2.2.2 Short Items" says item info bits 2-7 describe the type and +        // function of the item. +        final byte itemType = (byte) (itemInfo & 0b1111_1100); +        return itemType == usagePageType && itemData == brailleDisplayUsagePage;      }      /** 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 b322dd709c2d..aec3f451fac6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java @@ -17,6 +17,7 @@  package com.android.server.accessibility;  import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage;  import static org.junit.Assert.assertThrows;  import static org.mockito.ArgumentMatchers.anyInt; @@ -33,17 +34,24 @@ import android.testing.DexmakerShareClassLoaderRule;  import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.util.HexDump; +  import com.google.common.truth.Expect;  import org.junit.Before;  import org.junit.Rule;  import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized;  import org.mockito.Mock;  import org.mockito.Mockito;  import org.mockito.MockitoAnnotations;  import java.io.File;  import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection;  import java.util.List;  /** @@ -51,184 +59,265 @@ import java.util.List;   *   * <p>Prefer adding new tests in CTS where possible.   */ +@RunWith(Enclosed.class)  public class BrailleDisplayConnectionTest { -    private static final Path NULL_PATH = Path.of("/dev/null"); - -    private BrailleDisplayConnection mBrailleDisplayConnection; -    @Mock -    private BrailleDisplayConnection.NativeInterface mNativeInterface; -    @Mock -    private AccessibilityServiceConnection mServiceConnection; - -    @Rule -    public final Expect expect = Expect.create(); - -    private Context mContext; - -    // To mock package-private class -    @Rule -    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = -            new DexmakerShareClassLoaderRule(); - -    @Before -    public void setup() { -        MockitoAnnotations.initMocks(this); -        mContext = InstrumentationRegistry.getInstrumentation().getContext(); -        when(mServiceConnection.isConnectedLocked()).thenReturn(true); -        mBrailleDisplayConnection = -                spy(new BrailleDisplayConnection(new Object(), mServiceConnection)); -    } -    @Test -    public void defaultNativeScanner_getHidrawNodePaths_returnsHidrawPaths() throws Exception { -        File testDir = mContext.getFilesDir(); -        Path hidrawNode0 = Path.of(testDir.getPath(), "hidraw0"); -        Path hidrawNode1 = Path.of(testDir.getPath(), "hidraw1"); -        Path otherDevice = Path.of(testDir.getPath(), "otherDevice"); -        Path[] nodePaths = {hidrawNode0, hidrawNode1, otherDevice}; -        try { -            for (Path node : nodePaths) { -                assertThat(node.toFile().createNewFile()).isTrue(); +    public static class ScannerTest { +        private static final Path NULL_PATH = Path.of("/dev/null"); + +        private BrailleDisplayConnection mBrailleDisplayConnection; +        @Mock +        private BrailleDisplayConnection.NativeInterface mNativeInterface; +        @Mock +        private AccessibilityServiceConnection mServiceConnection; + +        @Rule +        public final Expect expect = Expect.create(); + +        private Context mContext; + +        // To mock package-private class +        @Rule +        public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = +                new DexmakerShareClassLoaderRule(); + +        @Before +        public void setup() { +            MockitoAnnotations.initMocks(this); +            mContext = InstrumentationRegistry.getInstrumentation().getContext(); +            when(mServiceConnection.isConnectedLocked()).thenReturn(true); +            mBrailleDisplayConnection = +                    spy(new BrailleDisplayConnection(new Object(), mServiceConnection)); +        } + +        @Test +        public void defaultNativeScanner_getHidrawNodePaths_returnsHidrawPaths() throws Exception { +            File testDir = mContext.getFilesDir(); +            Path hidrawNode0 = Path.of(testDir.getPath(), "hidraw0"); +            Path hidrawNode1 = Path.of(testDir.getPath(), "hidraw1"); +            Path otherDevice = Path.of(testDir.getPath(), "otherDevice"); +            Path[] nodePaths = {hidrawNode0, hidrawNode1, otherDevice}; +            try { +                for (Path node : nodePaths) { +                    assertThat(node.toFile().createNewFile()).isTrue(); +                } + +                BrailleDisplayConnection.BrailleDisplayScanner scanner = +                        mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); + +                assertThat(scanner.getHidrawNodePaths(testDir.toPath())) +                        .containsExactly(hidrawNode0, hidrawNode1); +            } finally { +                for (Path node : nodePaths) { +                    node.toFile().delete(); +                }              } +        } + +        @Test +        public void defaultNativeScanner_getReportDescriptor_returnsDescriptor() { +            int descriptorSize = 4; +            byte[] descriptor = {0xB, 0xE, 0xE, 0xF}; +            when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(descriptorSize); +            when(mNativeInterface.getHidrawDesc(anyInt(), eq(descriptorSize))).thenReturn( +                    descriptor);              BrailleDisplayConnection.BrailleDisplayScanner scanner =                      mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); -            assertThat(scanner.getHidrawNodePaths(testDir.toPath())) -                    .containsExactly(hidrawNode0, hidrawNode1); -        } finally { -            for (Path node : nodePaths) { -                node.toFile().delete(); -            } +            assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isEqualTo(descriptor);          } -    } -    @Test -    public void defaultNativeScanner_getReportDescriptor_returnsDescriptor() { -        int descriptorSize = 4; -        byte[] descriptor = {0xB, 0xE, 0xE, 0xF}; -        when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(descriptorSize); -        when(mNativeInterface.getHidrawDesc(anyInt(), eq(descriptorSize))).thenReturn(descriptor); +        @Test +        public void defaultNativeScanner_getReportDescriptor_invalidSize_returnsNull() { +            when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(0); -        BrailleDisplayConnection.BrailleDisplayScanner scanner = -                mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); +            BrailleDisplayConnection.BrailleDisplayScanner scanner = +                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); -        assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isEqualTo(descriptor); -    } +            assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isNull(); +        } -    @Test -    public void defaultNativeScanner_getReportDescriptor_invalidSize_returnsNull() { -        when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(0); +        @Test +        public void defaultNativeScanner_getUniqueId_returnsUniq() { +            String macAddress = "12:34:56:78"; +            when(mNativeInterface.getHidrawUniq(anyInt())).thenReturn(macAddress); -        BrailleDisplayConnection.BrailleDisplayScanner scanner = -                mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); +            BrailleDisplayConnection.BrailleDisplayScanner scanner = +                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); -        assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isNull(); -    } +            assertThat(scanner.getUniqueId(NULL_PATH)).isEqualTo(macAddress); +        } + +        @Test +        public void defaultNativeScanner_getDeviceBusType_busUsb() { +            when(mNativeInterface.getHidrawBusType(anyInt())) +                    .thenReturn(BrailleDisplayConnection.BUS_USB); -    @Test -    public void defaultNativeScanner_getUniqueId_returnsUniq() { -        String macAddress = "12:34:56:78"; -        when(mNativeInterface.getHidrawUniq(anyInt())).thenReturn(macAddress); +            BrailleDisplayConnection.BrailleDisplayScanner scanner = +                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); -        BrailleDisplayConnection.BrailleDisplayScanner scanner = -                mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); +            assertThat(scanner.getDeviceBusType(NULL_PATH)) +                    .isEqualTo(BrailleDisplayConnection.BUS_USB); +        } -        assertThat(scanner.getUniqueId(NULL_PATH)).isEqualTo(macAddress); -    } +        @Test +        public void defaultNativeScanner_getDeviceBusType_busBluetooth() { +            when(mNativeInterface.getHidrawBusType(anyInt())) +                    .thenReturn(BrailleDisplayConnection.BUS_BLUETOOTH); -    @Test -    public void defaultNativeScanner_getDeviceBusType_busUsb() { -        when(mNativeInterface.getHidrawBusType(anyInt())) -                .thenReturn(BrailleDisplayConnection.BUS_USB); +            BrailleDisplayConnection.BrailleDisplayScanner scanner = +                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); -        BrailleDisplayConnection.BrailleDisplayScanner scanner = -                mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); +            assertThat(scanner.getDeviceBusType(NULL_PATH)) +                    .isEqualTo(BrailleDisplayConnection.BUS_BLUETOOTH); +        } -        assertThat(scanner.getDeviceBusType(NULL_PATH)) -                .isEqualTo(BrailleDisplayConnection.BUS_USB); -    } +        @Test +        public void write_bypassesServiceSideCheckWithLargeBuffer_disconnects() { +            Mockito.doNothing().when(mBrailleDisplayConnection).disconnect(); +            mBrailleDisplayConnection.write( +                    new byte[IBinder.getSuggestedMaxIpcSizeBytes() * 2]); -    @Test -    public void defaultNativeScanner_getDeviceBusType_busBluetooth() { -        when(mNativeInterface.getHidrawBusType(anyInt())) -                .thenReturn(BrailleDisplayConnection.BUS_BLUETOOTH); +            verify(mBrailleDisplayConnection).disconnect(); +        } -        BrailleDisplayConnection.BrailleDisplayScanner scanner = -                mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); +        @Test +        public void write_notConnected_throwsIllegalStateException() { +            when(mServiceConnection.isConnectedLocked()).thenReturn(false); -        assertThat(scanner.getDeviceBusType(NULL_PATH)) -                .isEqualTo(BrailleDisplayConnection.BUS_BLUETOOTH); -    } +            assertThrows(IllegalStateException.class, +                    () -> mBrailleDisplayConnection.write(new byte[1])); +        } -    @Test -    public void write_bypassesServiceSideCheckWithLargeBuffer_disconnects() { -        Mockito.doNothing().when(mBrailleDisplayConnection).disconnect(); -        mBrailleDisplayConnection.write( -                new byte[IBinder.getSuggestedMaxIpcSizeBytes() * 2]); +        @Test +        public void write_unableToCreateWriteStream_disconnects() { +            Mockito.doNothing().when(mBrailleDisplayConnection).disconnect(); +            // mBrailleDisplayConnection#connectLocked was never called so the +            // connection's mHidrawNode is still null. This will throw an exception +            // when attempting to create FileOutputStream on the node. +            mBrailleDisplayConnection.write(new byte[1]); -        verify(mBrailleDisplayConnection).disconnect(); -    } +            verify(mBrailleDisplayConnection).disconnect(); +        } -    @Test -    public void write_notConnected_throwsIllegalStateException() { -        when(mServiceConnection.isConnectedLocked()).thenReturn(false); +        // 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. + +        @Test +        public void setTestData_scannerReturnsTestData() { +            Bundle bd1 = new Bundle(), bd2 = new Bundle(); + +            Path path1 = Path.of("/dev/path1"), path2 = Path.of("/dev/path2"); +            bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH, +                    path1.toString()); +            bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH, +                    path2.toString()); +            byte[] desc1 = {0xB, 0xE}, desc2 = {0xE, 0xF}; +            bd1.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, desc1); +            bd2.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, desc2); +            String uniq1 = "uniq1", uniq2 = "uniq2"; +            bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq1); +            bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq2); +            int bus1 = BrailleDisplayConnection.BUS_USB, bus2 = +                    BrailleDisplayConnection.BUS_BLUETOOTH; +            bd1.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, +                    bus1 == BrailleDisplayConnection.BUS_BLUETOOTH); +            bd2.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, +                    bus2 == BrailleDisplayConnection.BUS_BLUETOOTH); -        assertThrows(IllegalStateException.class, -                () -> mBrailleDisplayConnection.write(new byte[1])); -    } +            BrailleDisplayConnection.BrailleDisplayScanner scanner = +                    mBrailleDisplayConnection.setTestData(List.of(bd1, bd2)); + +            expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).containsExactly(path1, path2); +            expect.that(scanner.getDeviceReportDescriptor(path1)).isEqualTo(desc1); +            expect.that(scanner.getDeviceReportDescriptor(path2)).isEqualTo(desc2); +            expect.that(scanner.getUniqueId(path1)).isEqualTo(uniq1); +            expect.that(scanner.getUniqueId(path2)).isEqualTo(uniq2); +            expect.that(scanner.getDeviceBusType(path1)).isEqualTo(bus1); +            expect.that(scanner.getDeviceBusType(path2)).isEqualTo(bus2); +        } -    @Test -    public void write_unableToCreateWriteStream_disconnects() { -        Mockito.doNothing().when(mBrailleDisplayConnection).disconnect(); -        // mBrailleDisplayConnection#connectLocked was never called so the -        // connection's mHidrawNode is still null. This will throw an exception -        // when attempting to create FileOutputStream on the node. -        mBrailleDisplayConnection.write(new byte[1]); +        @Test +        public void setTestData_emptyTestData_returnsNullNodePaths() { +            BrailleDisplayConnection.BrailleDisplayScanner scanner = +                    mBrailleDisplayConnection.setTestData(List.of()); -        verify(mBrailleDisplayConnection).disconnect(); +            expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).isNull(); +        }      } -    // 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. - -    @Test -    public void setTestData_scannerReturnsTestData() { -        Bundle bd1 = new Bundle(), bd2 = new Bundle(); - -        Path path1 = Path.of("/dev/path1"), path2 = Path.of("/dev/path2"); -        bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH, path1.toString()); -        bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH, path2.toString()); -        byte[] desc1 = {0xB, 0xE}, desc2 = {0xE, 0xF}; -        bd1.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, desc1); -        bd2.putByteArray(BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR, desc2); -        String uniq1 = "uniq1", uniq2 = "uniq2"; -        bd1.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq1); -        bd2.putString(BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID, uniq2); -        int bus1 = BrailleDisplayConnection.BUS_USB, bus2 = BrailleDisplayConnection.BUS_BLUETOOTH; -        bd1.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, -                bus1 == BrailleDisplayConnection.BUS_BLUETOOTH); -        bd2.putBoolean(BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH, -                bus2 == BrailleDisplayConnection.BUS_BLUETOOTH); - -        BrailleDisplayConnection.BrailleDisplayScanner scanner = -                mBrailleDisplayConnection.setTestData(List.of(bd1, bd2)); - -        expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).containsExactly(path1, path2); -        expect.that(scanner.getDeviceReportDescriptor(path1)).isEqualTo(desc1); -        expect.that(scanner.getDeviceReportDescriptor(path2)).isEqualTo(desc2); -        expect.that(scanner.getUniqueId(path1)).isEqualTo(uniq1); -        expect.that(scanner.getUniqueId(path2)).isEqualTo(uniq2); -        expect.that(scanner.getDeviceBusType(path1)).isEqualTo(bus1); -        expect.that(scanner.getDeviceBusType(path2)).isEqualTo(bus2); -    } +    @RunWith(Parameterized.class) +    public static class BrailleDisplayDescriptorTest { +        @Parameterized.Parameters(name = "{0}") +        public static Collection<Object[]> data() { +            return Arrays.asList(new Object[][]{ +                    {"match_BdPage", new byte[]{ +                            // Just one item, defines the BD page +                            0x05, 0x41}}, +                    {"match_BdPageAfterAnotherPage", new byte[]{ +                            // One item defines another page +                            0x05, 0x01, +                            // Next item defines BD page +                            0x05, 0x41}}, +                    {"match_BdPageAfterSizeZeroItem", new byte[]{ +                            // Size-zero item (last 2 bits are 00) +                            0x00, +                            // Next item defines BD page +                            0x05, 0x41}}, +                    {"match_BdPageAfterSizeOneItem", new byte[]{ +                            // Size-one item (last 2 bits are 01) +                            0x01, 0x7F, +                            // Next item defines BD page +                            0x05, 0x41}}, +                    {"match_BdPageAfterSizeTwoItem", new byte[]{ +                            // Size-two item (last 2 bits are 10) +                            0x02, 0x7F, 0x7F, +                            0x05, 0x41}}, +                    {"match_BdPageAfterSizeFourItem", new byte[]{ +                            // Size-four item (last 2 bits are 11) +                            0x03, 0x7F, 0x7F, 0x7F, 0x7F, +                            0x05, 0x41}}, +                    {"match_BdPageInBetweenOtherPages", new byte[]{ +                            // One item defines another page +                            0x05, 0x01, +                            // Next item defines BD page +                            0x05, 0x41, +                            // Next item defines another page +                            0x05, 0x02}}, +                    {"fail_OtherPage", new byte[]{ +                            // Just one item, defines another page +                            0x05, 0x01}}, +                    {"fail_BdPageBeforeMissingData", new byte[]{ +                            // This item defines BD page +                            0x05, 0x41, +                            // Next item specifies size-one item (last 2 bits are 01) but +                            // that one data byte is missing; this descriptor is malformed. +                            0x01}}, +                    {"fail_BdPageWithWrongDataSize", new byte[]{ +                            // This item defines a page with two-byte ID 0x41 0x7F, not 0x41. +                            0x06, 0x41, 0x7F}}, +                    {"fail_LongItem", new byte[]{ +                            // Item has type bits 1111, indicating Long Item. +                            (byte) 0xF0}}, +            }); +        } -    @Test -    public void setTestData_emptyTestData_returnsNullNodePaths() { -        BrailleDisplayConnection.BrailleDisplayScanner scanner = -                mBrailleDisplayConnection.setTestData(List.of()); -        expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).isNull(); +        @Parameterized.Parameter(0) +        public String mTestName; +        @Parameterized.Parameter(1) +        public byte[] mDescriptor; + +        @Test +        public void isBrailleDisplay() { +            final boolean expectedMatch = mTestName.startsWith("match_"); +            assertWithMessage( +                    "Expected isBrailleDisplay==" + expectedMatch +                            + " for descriptor " + HexDump.toHexString(mDescriptor)) +                    .that(BrailleDisplayConnection.isBrailleDisplay(mDescriptor)) +                    .isEqualTo(expectedMatch); +        }      }  } |