Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 211ef98d authored by Daniel Norman's avatar Daniel Norman
Browse files

Ensure that the BD's HID descriptor includes the BD usage page (0x41).

Bug: 316036493
Test: (CTS)      atest BrailleDisplayTest
Test: (Internal) atest BrailleDisplayConnectionTest
Change-Id: I191d1df8391d5b803f7fba0d2ee3f21c540abea5
parent a82202a5
Loading
Loading
Loading
Loading
+57 −3
Original line number Diff line number Diff line
@@ -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;
    }

    /**
+235 −146
Original line number Diff line number Diff line
@@ -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,7 +59,10 @@ import java.util.List;
 *
 * <p>Prefer adding new tests in CTS where possible.
 */
@RunWith(Enclosed.class)
public class BrailleDisplayConnectionTest {

    public static class ScannerTest {
        private static final Path NULL_PATH = Path.of("/dev/null");

        private BrailleDisplayConnection mBrailleDisplayConnection;
@@ -108,7 +119,8 @@ public class BrailleDisplayConnectionTest {
            int descriptorSize = 4;
            byte[] descriptor = {0xB, 0xE, 0xE, 0xF};
            when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(descriptorSize);
        when(mNativeInterface.getHidrawDesc(anyInt(), eq(descriptorSize))).thenReturn(descriptor);
            when(mNativeInterface.getHidrawDesc(anyInt(), eq(descriptorSize))).thenReturn(
                    descriptor);

            BrailleDisplayConnection.BrailleDisplayScanner scanner =
                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
@@ -198,15 +210,18 @@ public class BrailleDisplayConnectionTest {
            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());
            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;
            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,
@@ -232,3 +247,77 @@ public class BrailleDisplayConnectionTest {
            expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).isNull();
        }
    }

    @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}},
            });
        }


        @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);
        }
    }
}