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

Commit 7574da8c authored by Daniel Norman's avatar Daniel Norman Committed by Android (Google) Code Review
Browse files

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

parents 13d7de07 211ef98d
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);
        }
    }
}