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

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

Merge changes from topic "b-311783331" into main

* changes:
  Add a `cmd accessibility` shell command to check HIDRAW access.
  Use a sysprop so connect APIs fail fast if HIDRAW is not supported.
parents 293e456a 4bc95c96
Loading
Loading
Loading
Loading
+30 −1
Original line number Diff line number Diff line
@@ -25,9 +25,11 @@ import android.hardware.usb.UsbDevice;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.Flags;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FunctionalUtils;

import java.io.IOException;
@@ -36,24 +38,46 @@ import java.util.concurrent.Executor;

/**
 * Default implementation of {@link BrailleDisplayController}.
 *
 * @hide
 */
// BrailleDisplayControllerImpl is not an API, but it implements BrailleDisplayController APIs.
// This @FlaggedApi annotation tells the linter that this method delegates API checks to its
// callers.
@FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
final class BrailleDisplayControllerImpl implements BrailleDisplayController {
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class BrailleDisplayControllerImpl implements BrailleDisplayController {

    private final AccessibilityService mAccessibilityService;
    private final Object mLock;
    private final boolean mIsHidrawSupported;

    private IBrailleDisplayConnection mBrailleDisplayConnection;
    private Executor mCallbackExecutor;
    private BrailleDisplayCallback mCallback;

    /**
     * Read-only property that returns whether HIDRAW access is supported on this device.
     *
     * <p>Defaults to true.
     *
     * <p>Device manufacturers without HIDRAW kernel support can set this to false in
     * the device's product makefile.
     */
    private static final boolean IS_HIDRAW_SUPPORTED = SystemProperties.getBoolean(
            "ro.accessibility.support_hidraw", true);

    BrailleDisplayControllerImpl(AccessibilityService accessibilityService,
            Object lock) {
        this(accessibilityService, lock, IS_HIDRAW_SUPPORTED);
    }

    @VisibleForTesting
    public BrailleDisplayControllerImpl(AccessibilityService accessibilityService,
            Object lock, boolean isHidrawSupported) {
        mAccessibilityService = accessibilityService;
        mLock = lock;
        mIsHidrawSupported = isHidrawSupported;
    }

    @Override
@@ -113,6 +137,11 @@ final class BrailleDisplayControllerImpl implements BrailleDisplayController {
                    createConnection,
            @NonNull Executor callbackExecutor, @NonNull BrailleDisplayCallback callback) {
        BrailleDisplayController.checkApiFlagIsEnabled();
        if (!mIsHidrawSupported) {
            callbackExecutor.execute(() -> callback.onConnectionFailed(
                    BrailleDisplayCallback.FLAG_ERROR_CANNOT_ACCESS));
            return;
        }
        if (isConnected()) {
            throw new IllegalStateException(
                    "This service already has a connected Braille display");
+20 −4
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;

import android.hardware.usb.UsbDevice;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -58,6 +59,7 @@ public class BrailleDisplayControllerImplTest {
    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    private AccessibilityService mAccessibilityService;
    private BrailleDisplayController mBrailleDisplayController;

    @Mock
@@ -76,12 +78,13 @@ public class BrailleDisplayControllerImplTest {
    @Before
    public void test() {
        MockitoAnnotations.initMocks(this);
        AccessibilityService accessibilityService = spy(new TestAccessibilityService());
        doReturn((Executor) Runnable::run).when(accessibilityService).getMainExecutor();
        doReturn(TEST_SERVICE_CONNECTION_ID).when(accessibilityService).getConnectionId();
        mAccessibilityService = spy(new TestAccessibilityService());
        doReturn((Executor) Runnable::run).when(mAccessibilityService).getMainExecutor();
        doReturn(TEST_SERVICE_CONNECTION_ID).when(mAccessibilityService).getConnectionId();
        AccessibilityInteractionClient.addConnection(TEST_SERVICE_CONNECTION_ID,
                mAccessibilityServiceConnection, /*initializeCache=*/false);
        mBrailleDisplayController = accessibilityService.getBrailleDisplayController();
        mBrailleDisplayController = new BrailleDisplayControllerImpl(
                mAccessibilityService, new Object(), /*isHidrawSupported=*/true);
    }

    // Automated CTS tests only use the BluetoothDevice version of BrailleDisplayController#connect
@@ -104,4 +107,17 @@ public class BrailleDisplayControllerImplTest {
        assertThrows(IllegalStateException.class,
                () -> mBrailleDisplayController.connect(usbDevice, mBrailleDisplayCallback));
    }

    @Test
    public void connect_HidrawNotSupported_callsOnConnectionFailed() {
        BrailleDisplayController controller = new BrailleDisplayControllerImpl(
                mAccessibilityService, new Object(), /*isHidrawSupported=*/false);
        UsbDevice usbDevice = Mockito.mock(UsbDevice.class);

        controller.connect(usbDevice, mBrailleDisplayCallback);

        verify(mBrailleDisplayCallback).onConnectionFailed(
                BrailleDisplayController.BrailleDisplayCallback.FLAG_ERROR_CANNOT_ACCESS);
        verifyZeroInteractions(mAccessibilityServiceConnection);
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -4659,8 +4659,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
    public void onShellCommand(FileDescriptor in, FileDescriptor out,
            FileDescriptor err, String[] args, ShellCallback callback,
            ResultReceiver resultReceiver) {
        new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args,
                callback, resultReceiver);
        new AccessibilityShellCommand(mContext, this, mSystemActionPerformer)
                .exec(this, in, out, err, args, callback, resultReceiver);
    }

    private final class InteractionBridge {
+80 −4
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package com.android.server.accessibility;

import android.Manifest;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Binder;
import android.os.Process;
import android.os.ShellCommand;
@@ -26,18 +28,27 @@ import android.os.UserHandle;
import com.android.server.LocalServices;
import com.android.server.wm.WindowManagerInternal;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Shell command implementation for the accessibility manager service
 */
final class AccessibilityShellCommand extends ShellCommand {
    final @NonNull AccessibilityManagerService mService;
    final @NonNull SystemActionPerformer mSystemActionPerformer;
    final @NonNull WindowManagerInternal mWindowManagerService;
    @NonNull
    final Context mContext;
    @NonNull
    final AccessibilityManagerService mService;
    @NonNull
    final SystemActionPerformer mSystemActionPerformer;
    @NonNull
    final WindowManagerInternal mWindowManagerService;

    AccessibilityShellCommand(@NonNull AccessibilityManagerService service,
    AccessibilityShellCommand(@NonNull Context context,
            @NonNull AccessibilityManagerService service,
            @NonNull SystemActionPerformer systemActionPerformer) {
        mContext = context;
        mService = service;
        mSystemActionPerformer = systemActionPerformer;
        mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
@@ -61,6 +72,8 @@ final class AccessibilityShellCommand extends ShellCommand {
            case "start-trace":
            case "stop-trace":
                return mService.getTraceManager().onShellCommand(cmd, this);
            case "check-hidraw":
                return checkHidraw();
        }
        return -1;
    }
@@ -106,6 +119,67 @@ final class AccessibilityShellCommand extends ShellCommand {
        return -1;
    }

    private int checkHidraw() {
        mContext.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
                "Missing MANAGE_ACCESSIBILITY permission");
        String subcommand = getNextArgRequired();
        File hidrawNode = new File(getNextArgRequired());
        switch (subcommand) {
            case "read" -> {
                return checkHidrawRead(hidrawNode);
            }
            case "write" -> {
                return checkHidrawWrite(hidrawNode);
            }
            case "descriptor" -> {
                return checkHidrawDescriptor(hidrawNode);
            }
            default -> {
                getErrPrintWriter().print("Unknown subcommand " + subcommand);
                return -1;
            }
        }
    }

    private int checkHidrawRead(File hidrawNode) {
        if (!hidrawNode.canRead()) {
            getErrPrintWriter().println("Unable to read from " + hidrawNode);
            return -1;
        }
        // Tests executing this command using UiAutomation#executeShellCommand will not receive
        // the command's exit value, so print the path to stdout to indicate success.
        getOutPrintWriter().print(hidrawNode.getAbsolutePath());
        return 0;
    }

    private int checkHidrawWrite(File hidrawNode) {
        if (!hidrawNode.canWrite()) {
            getErrPrintWriter().println("Unable to write to " + hidrawNode);
            return -1;
        }
        // Tests executing this command using UiAutomation#executeShellCommand will not receive
        // the command's exit value, so print the path to stdout to indicate success.
        getOutPrintWriter().print(hidrawNode.getAbsolutePath());
        return 0;
    }

    private int checkHidrawDescriptor(File hidrawNode) {
        BrailleDisplayConnection.BrailleDisplayScanner scanner =
                BrailleDisplayConnection.createScannerForShell();
        byte[] descriptor = scanner.getDeviceReportDescriptor(hidrawNode.toPath());
        if (descriptor == null) {
            getErrPrintWriter().println("Unable to read descriptor for " + hidrawNode);
            return -1;
        }
        try {
            // Print the descriptor bytes to stdout.
            getRawOutputStream().write(descriptor);
            return 0;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Integer parseUserId() {
        final String option = getNextOption();
        if (option != null) {
@@ -131,6 +205,8 @@ final class AccessibilityShellCommand extends ShellCommand {
        pw.println("    Get whether binding to services provided by instant apps is allowed.");
        pw.println("  call-system-action <ACTION_ID>");
        pw.println("    Calls the system action with the given action id.");
        pw.println("  check-hidraw [read|write|descriptor] <HIDRAW_NODE_PATH>");
        pw.println("    Checks if the system can perform various actions on the HIDRAW node.");
        mService.getTraceManager().onHelp(pw);
    }
}
+13 −7
Original line number Diff line number Diff line
@@ -115,6 +115,13 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
        this.mServiceConnection = Objects.requireNonNull(serviceConnection);
    }

    /**
     * Used for `cmd accessibility` to check hidraw access.
     */
    static BrailleDisplayScanner createScannerForShell() {
        return getDefaultNativeScanner(new DefaultNativeInterface());
    }

    /**
     * Interface to scan for properties of connected Braille displays.
     *
@@ -125,7 +132,6 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
     * @see #getDefaultNativeScanner
     * @see #setTestData
     */
    @VisibleForTesting
    interface BrailleDisplayScanner {
        Collection<Path> getHidrawNodePaths(@NonNull Path directory);

@@ -441,7 +447,7 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
     * from HIDRAW nodes and perform ioctls using the provided {@link NativeInterface}.
     */
    @VisibleForTesting
    BrailleDisplayScanner getDefaultNativeScanner(@NonNull NativeInterface nativeInterface) {
    static BrailleDisplayScanner getDefaultNativeScanner(@NonNull NativeInterface nativeInterface) {
        Objects.requireNonNull(nativeInterface);
        return new BrailleDisplayScanner() {
            private static final String HIDRAW_DEVICE_GLOB = "hidraw*";
@@ -576,7 +582,7 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
    }

    /** Native interface that actually calls native HIDRAW ioctls. */
    private class DefaultNativeInterface implements NativeInterface {
    private static class DefaultNativeInterface implements NativeInterface {
        @Override
        public int getHidrawDescSize(int fd) {
            return nativeGetHidrawDescSize(fd);
@@ -598,11 +604,11 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
        }
    }

    private native int nativeGetHidrawDescSize(int fd);
    private static native int nativeGetHidrawDescSize(int fd);

    private native byte[] nativeGetHidrawDesc(int fd, int descSize);
    private static native byte[] nativeGetHidrawDesc(int fd, int descSize);

    private native String nativeGetHidrawUniq(int fd);
    private static native String nativeGetHidrawUniq(int fd);

    private native int nativeGetHidrawBusType(int fd);
    private static native int nativeGetHidrawBusType(int fd);
}
Loading