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

Commit 4bc95c96 authored by Daniel Norman's avatar Daniel Norman
Browse files

Add a `cmd accessibility` shell command to check HIDRAW access.

This is used by GTS to confirm that system_server has access to interact
with hidraw nodes, as needed for the BrailleDisplayController API.

Bug: 311783331
Test: atest GtsAccessibilityTestCases
Change-Id: Iefef117cf916f7e9e2de2f52a6fa38ba355becb6
parent d8163ff7
Loading
Loading
Loading
Loading
+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);
}
+6 −8
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ constexpr int UNIQ_SIZE_MAX = 64;
} // anonymous namespace

static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawDescSize(
        JNIEnv* env, jobject thiz, int fd) {
        JNIEnv* env, jclass /*clazz*/, int fd) {
    int size = 0;
    if (ioctl(fd, HIDIOCGRDESCSIZE, &size) < 0) {
        return -1;
@@ -49,7 +49,7 @@ static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawD
}

static jbyteArray com_android_server_accessibility_BrailleDisplayConnection_getHidrawDesc(
        JNIEnv* env, jobject thiz, int fd, int descSize) {
        JNIEnv* env, jclass /*clazz*/, int fd, int descSize) {
    struct hidraw_report_descriptor desc;
    desc.size = descSize;
    if (ioctl(fd, HIDIOCGRDESC, &desc) < 0) {
@@ -63,9 +63,8 @@ static jbyteArray com_android_server_accessibility_BrailleDisplayConnection_getH
    return result;
}

static jstring com_android_server_accessibility_BrailleDisplayConnection_getHidrawUniq(JNIEnv* env,
                                                                                       jobject thiz,
                                                                                       int fd) {
static jstring com_android_server_accessibility_BrailleDisplayConnection_getHidrawUniq(
        JNIEnv* env, jclass /*clazz*/, int fd) {
    char buf[UNIQ_SIZE_MAX];
    if (ioctl(fd, HIDIOCGRAWUNIQ(UNIQ_SIZE_MAX), buf) < 0) {
        return nullptr;
@@ -74,9 +73,8 @@ static jstring com_android_server_accessibility_BrailleDisplayConnection_getHidr
    return env->NewStringUTF(buf);
}

static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawBusType(JNIEnv* env,
                                                                                       jobject thiz,
                                                                                       int fd) {
static jint com_android_server_accessibility_BrailleDisplayConnection_getHidrawBusType(
        JNIEnv* env, jclass /*clazz*/, int fd) {
    struct hidraw_devinfo info;
    if (ioctl(fd, HIDIOCGRAWINFO, &info) < 0) {
        return -1;
+6 −6
Original line number Diff line number Diff line
@@ -103,7 +103,7 @@ public class BrailleDisplayConnectionTest {
                }

                BrailleDisplayConnection.BrailleDisplayScanner scanner =
                        mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
                        BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);

                assertThat(scanner.getHidrawNodePaths(testDir.toPath()))
                        .containsExactly(hidrawNode0, hidrawNode1);
@@ -123,7 +123,7 @@ public class BrailleDisplayConnectionTest {
                    descriptor);

            BrailleDisplayConnection.BrailleDisplayScanner scanner =
                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);

            assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isEqualTo(descriptor);
        }
@@ -133,7 +133,7 @@ public class BrailleDisplayConnectionTest {
            when(mNativeInterface.getHidrawDescSize(anyInt())).thenReturn(0);

            BrailleDisplayConnection.BrailleDisplayScanner scanner =
                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);

            assertThat(scanner.getDeviceReportDescriptor(NULL_PATH)).isNull();
        }
@@ -144,7 +144,7 @@ public class BrailleDisplayConnectionTest {
            when(mNativeInterface.getHidrawUniq(anyInt())).thenReturn(macAddress);

            BrailleDisplayConnection.BrailleDisplayScanner scanner =
                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);

            assertThat(scanner.getUniqueId(NULL_PATH)).isEqualTo(macAddress);
        }
@@ -155,7 +155,7 @@ public class BrailleDisplayConnectionTest {
                    .thenReturn(BrailleDisplayConnection.BUS_USB);

            BrailleDisplayConnection.BrailleDisplayScanner scanner =
                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);

            assertThat(scanner.getDeviceBusType(NULL_PATH))
                    .isEqualTo(BrailleDisplayConnection.BUS_USB);
@@ -167,7 +167,7 @@ public class BrailleDisplayConnectionTest {
                    .thenReturn(BrailleDisplayConnection.BUS_BLUETOOTH);

            BrailleDisplayConnection.BrailleDisplayScanner scanner =
                    mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);
                    BrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface);

            assertThat(scanner.getDeviceBusType(NULL_PATH))
                    .isEqualTo(BrailleDisplayConnection.BUS_BLUETOOTH);