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

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

Merge changes from topic "hid-bd" into main

* changes:
  New AccessibilityService API to talk with HID Braille Displays.
  Adds JNI for accessibility in system_server to perform HIDRAW ioctls.
parents 54ff9fed e78750db
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -3322,11 +3322,13 @@ package android.accessibilityservice {
    method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
    method public boolean clearCache();
    method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void clearTestBrailleDisplayController();
    method public final void disableSelf();
    method public final boolean dispatchGesture(@NonNull android.accessibilityservice.GestureDescription, @Nullable android.accessibilityservice.AccessibilityService.GestureResultCallback, @Nullable android.os.Handler);
    method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
    method @NonNull public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController();
    method @NonNull public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController(int);
    method @FlaggedApi("android.view.accessibility.braille_display_hid") @NonNull public android.accessibilityservice.BrailleDisplayController getBrailleDisplayController();
    method @NonNull @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
    method @Nullable public final android.accessibilityservice.InputMethod getInputMethod();
    method @NonNull public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
@@ -3356,6 +3358,7 @@ package android.accessibilityservice {
    method public boolean setCacheEnabled(boolean);
    method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
    method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void setTestBrailleDisplayController(@NonNull android.accessibilityservice.BrailleDisplayController);
    method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
    method public void takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
    method public void takeScreenshotOfWindow(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
@@ -3560,6 +3563,25 @@ package android.accessibilityservice {
    field public String[] packageNames;
  }
  @FlaggedApi("android.view.accessibility.braille_display_hid") public interface BrailleDisplayController {
    method @FlaggedApi("android.view.accessibility.braille_display_hid") @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void connect(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.accessibilityservice.BrailleDisplayController.BrailleDisplayCallback);
    method @FlaggedApi("android.view.accessibility.braille_display_hid") @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void connect(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.BrailleDisplayController.BrailleDisplayCallback);
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void connect(@NonNull android.hardware.usb.UsbDevice, @NonNull android.accessibilityservice.BrailleDisplayController.BrailleDisplayCallback);
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void connect(@NonNull android.hardware.usb.UsbDevice, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.BrailleDisplayController.BrailleDisplayCallback);
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void disconnect();
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public boolean isConnected();
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void write(@NonNull byte[]) throws java.io.IOException;
  }
  @FlaggedApi("android.view.accessibility.braille_display_hid") public static interface BrailleDisplayController.BrailleDisplayCallback {
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void onConnected(@NonNull byte[]);
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void onConnectionFailed(int);
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void onDisconnected();
    method @FlaggedApi("android.view.accessibility.braille_display_hid") public void onInput(@NonNull byte[]);
    field @FlaggedApi("android.view.accessibility.braille_display_hid") public static final int FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND = 2; // 0x2
    field @FlaggedApi("android.view.accessibility.braille_display_hid") public static final int FLAG_ERROR_CANNOT_ACCESS = 1; // 0x1
  }
  public final class FingerprintGestureController {
    method public boolean isGestureDetectionAvailable();
    method public void registerFingerprintGestureCallback(@NonNull android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback, @Nullable android.os.Handler);
+8 −0
Original line number Diff line number Diff line
@@ -104,6 +104,14 @@ package android.accessibilityservice {
    method @FlaggedApi("android.view.accessibility.motion_event_observing") public void setObservedMotionEventSources(int);
  }

  @FlaggedApi("android.view.accessibility.braille_display_hid") public interface BrailleDisplayController {
    method @FlaggedApi("android.view.accessibility.braille_display_hid") @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public static void setTestBrailleDisplayData(@NonNull android.accessibilityservice.AccessibilityService, @NonNull java.util.List<android.os.Bundle>);
    field @FlaggedApi("android.view.accessibility.braille_display_hid") public static final String TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH = "BUS_BLUETOOTH";
    field @FlaggedApi("android.view.accessibility.braille_display_hid") public static final String TEST_BRAILLE_DISPLAY_DESCRIPTOR = "DESCRIPTOR";
    field @FlaggedApi("android.view.accessibility.braille_display_hid") public static final String TEST_BRAILLE_DISPLAY_HIDRAW_PATH = "HIDRAW_PATH";
    field @FlaggedApi("android.view.accessibility.braille_display_hid") public static final String TEST_BRAILLE_DISPLAY_UNIQUE_ID = "UNIQUE_ID";
  }

}

package android.animation {
+55 −0
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -850,6 +851,8 @@ public abstract class AccessibilityService extends Service {
    private boolean mInputMethodInitialized = false;
    private final SparseArray<AccessibilityButtonController> mAccessibilityButtonControllers =
            new SparseArray<>(0);
    private BrailleDisplayController mBrailleDisplayController;
    private BrailleDisplayController mTestBrailleDisplayController;

    private int mGestureStatusCallbackSequence;

@@ -3634,4 +3637,56 @@ public abstract class AccessibilityService extends Service {
                .attachAccessibilityOverlayToWindow(
                        mConnectionId, accessibilityWindowId, sc, executor, callback);
    }

    /**
     * Returns the {@link BrailleDisplayController} which may be used to communicate with
     * refreshable Braille displays that provide USB or Bluetooth Braille display HID support.
     */
    @FlaggedApi(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
    @NonNull
    public BrailleDisplayController getBrailleDisplayController() {
        BrailleDisplayController.checkApiFlagIsEnabled();
        synchronized (mLock) {
            if (mTestBrailleDisplayController != null) {
                return mTestBrailleDisplayController;
            }

            if (mBrailleDisplayController == null) {
                mBrailleDisplayController = new BrailleDisplayControllerImpl(this, mLock);
            }
            return mBrailleDisplayController;
        }
    }

    /**
     * Set the {@link BrailleDisplayController} implementation that will be returned by
     * {@link #getBrailleDisplayController}, to allow this accessibility service to test its
     * interaction with BrailleDisplayController without requiring a real Braille display.
     *
     * <p>For full test fidelity, ensure that this test-only implementation follows the same
     * behavior specified in the documentation for {@link BrailleDisplayController}, including
     * thrown exceptions.
     *
     * @param controller A test-only implementation of {@link BrailleDisplayController}.
     */
    @FlaggedApi(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
    public void setTestBrailleDisplayController(@NonNull BrailleDisplayController controller) {
        BrailleDisplayController.checkApiFlagIsEnabled();
        Objects.requireNonNull(controller);
        synchronized (mLock) {
            mTestBrailleDisplayController = controller;
        }
    }

    /**
     * Clears the {@link BrailleDisplayController} previously set by
     * {@link #setTestBrailleDisplayController}.
     */
    @FlaggedApi(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
    public void clearTestBrailleDisplayController() {
        BrailleDisplayController.checkApiFlagIsEnabled();
        synchronized (mLock) {
            mTestBrailleDisplayController = null;
        }
    }
}
+308 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.accessibilityservice;

import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.bluetooth.BluetoothDevice;
import android.hardware.usb.UsbDevice;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.Flags;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * Used to communicate with a Braille display that supports the Braille display HID standard
 * (usage page 0x41).
 *
 * <p>Only one Braille display may be connected at a time.
 */
// This interface doesn't actually own resources. Its I/O connections are owned, monitored,
// and automatically closed by the system after the accessibility service is disconnected.
@SuppressLint("NotCloseable")
@FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
public interface BrailleDisplayController {

    /**
     * Throw {@link IllegalStateException} if this feature's aconfig flag is disabled.
     *
     * @hide
     */
    static void checkApiFlagIsEnabled() {
        if (!Flags.brailleDisplayHid()) {
            throw new IllegalStateException("Flag BRAILLE_DISPLAY_HID not enabled");
        }
    }

    /**
     * Interface provided to {@link BrailleDisplayController} connection methods to
     * receive callbacks from the system.
     */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    interface BrailleDisplayCallback {
        /**
         * The system cannot access connected HID devices.
         */
        @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
        int FLAG_ERROR_CANNOT_ACCESS = 1 << 0;
        /**
         * A unique Braille display matching the requested properties could not be identified.
         */
        @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
        int FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND = 1 << 1;

        /** @hide */
        @Retention(RetentionPolicy.SOURCE)
        @IntDef(flag = true, prefix = "FLAG_ERROR_", value = {
                FLAG_ERROR_CANNOT_ACCESS,
                FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND,
        })
        @interface ErrorCode {
        }

        /**
         * Callback to observe a successful Braille display connection.
         *
         * <p>The provided HID report descriptor should be used to understand the input bytes
         * received from the Braille display via {@link #onInput} and to prepare
         * the output sent to the Braille display via {@link #write}.
         *
         * @param hidDescriptor The HID report descriptor for this Braille display.
         * @see #connect(BluetoothDevice, BrailleDisplayCallback)
         * @see #connect(UsbDevice, BrailleDisplayCallback)
         */
        @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
        void onConnected(@NonNull byte[] hidDescriptor);

        /**
         * Callback to observe a failed Braille display connection.
         *
         * @param errorFlags A bitmask of error codes for the connection failure.
         * @see #connect(BluetoothDevice, BrailleDisplayCallback)
         * @see #connect(UsbDevice, BrailleDisplayCallback)
         */
        @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
        void onConnectionFailed(@ErrorCode int errorFlags);

        /**
         * Callback to observe input bytes from the currently connected Braille display.
         *
         * @param input The input bytes from the Braille display, formatted according to the HID
         *              report descriptor and the HIDRAW kernel driver.
         */
        @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
        void onInput(@NonNull byte[] input);

        /**
         * Callback to observe when the currently connected Braille display is disconnected by the
         * system.
         */
        @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
        void onDisconnected();
    }

    /**
     * Connects to the requested bluetooth Braille display using the Braille
     * display HID standard (usage page 0x41).
     *
     * <p>If successful then the HID report descriptor will be provided to
     * {@link BrailleDisplayCallback#onConnected}
     * and the Braille display will start sending incoming input bytes to
     * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
     * then the system will disconnect the Braille display.
     *
     * <p>Note that the callbacks will be executed on the main thread using
     * {@link AccessibilityService#getMainExecutor()}. To specify the execution thread, use
     * {@link #connect(BluetoothDevice, Executor, BrailleDisplayCallback)}.
     *
     * @param bluetoothDevice The Braille display device.
     * @param callback        Callbacks used to provide connection results.
     * @see BrailleDisplayCallback#onConnected
     * @see BrailleDisplayCallback#onConnectionFailed
     * @throws IllegalStateException if a Braille display is already connected to this controller.
     */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    void connect(@NonNull BluetoothDevice bluetoothDevice,
            @NonNull BrailleDisplayCallback callback);

    /**
     * Connects to the requested bluetooth Braille display using the Braille
     * display HID standard (usage page 0x41).
     *
     * <p>If successful then the HID report descriptor will be provided to
     * {@link BrailleDisplayCallback#onConnected}
     * and the Braille display will start sending incoming input bytes to
     * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
     * then the system will disconnect the Braille display.
     *
     * @param bluetoothDevice  The Braille display device.
     * @param callbackExecutor Executor for executing the provided callbacks.
     * @param callback         Callbacks used to provide connection results.
     * @see BrailleDisplayCallback#onConnected
     * @see BrailleDisplayCallback#onConnectionFailed
     * @throws IllegalStateException if a Braille display is already connected to this controller.
     */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    void connect(@NonNull BluetoothDevice bluetoothDevice,
            @NonNull @CallbackExecutor Executor callbackExecutor,
            @NonNull BrailleDisplayCallback callback);

    /**
     * Connects to the requested USB Braille display using the Braille
     * display HID standard (usage page 0x41).
     *
     * <p>If successful then the HID report descriptor will be provided to
     * {@link BrailleDisplayCallback#onConnected}
     * and the Braille display will start sending incoming input bytes to
     * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
     * then the system will disconnect the Braille display.
     *
     * <p>The accessibility service app must already have approval to access the USB device
     * from the standard {@link android.hardware.usb.UsbManager} access approval process.
     *
     * <p>Note that the callbacks will be executed on the main thread using
     * {@link AccessibilityService#getMainExecutor()}. To specify the execution thread, use
     * {@link #connect(UsbDevice, Executor, BrailleDisplayCallback)}.
     *
     * @param usbDevice        The Braille display device.
     * @param callback         Callbacks used to provide connection results.
     * @see BrailleDisplayCallback#onConnected
     * @see BrailleDisplayCallback#onConnectionFailed
     * @throws SecurityException if the caller does not have USB device approval.
     * @throws IllegalStateException if a Braille display is already connected to this controller.
     */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    void connect(@NonNull UsbDevice usbDevice,
            @NonNull BrailleDisplayCallback callback);

    /**
     * Connects to the requested USB Braille display using the Braille
     * display HID standard (usage page 0x41).
     *
     * <p>If successful then the HID report descriptor will be provided to
     * {@link BrailleDisplayCallback#onConnected}
     * and the Braille display will start sending incoming input bytes to
     * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
     * then the system will disconnect the Braille display.
     *
     * <p>The accessibility service app must already have approval to access the USB device
     * from the standard {@link android.hardware.usb.UsbManager} access approval process.
     *
     * @param usbDevice        The Braille display device.
     * @param callbackExecutor Executor for executing the provided callbacks.
     * @param callback         Callbacks used to provide connection results.
     * @see BrailleDisplayCallback#onConnected
     * @see BrailleDisplayCallback#onConnectionFailed
     * @throws SecurityException if the caller does not have USB device approval.
     * @throws IllegalStateException if a Braille display is already connected to this controller.
     */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    void connect(@NonNull UsbDevice usbDevice,
            @NonNull @CallbackExecutor Executor callbackExecutor,
            @NonNull BrailleDisplayCallback callback);

    /**
     * Returns true if a Braille display is currently connected, otherwise false.
     *
     * @see #connect
     */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    boolean isConnected();

    /**
     * Writes a HID report to the currently connected Braille display.
     *
     * <p>This method returns immediately after dispatching the write request to the system.
     * If the system experiences an error in writing output (e.g. the Braille display is unplugged
     * after the system receives the write request but before writing the bytes to the Braille
     * display) then the system will disconnect the Braille display, which calls
     * {@link BrailleDisplayCallback#onDisconnected()}.
     *
     * @param buffer The bytes to write to the Braille display. These bytes should be formatted
     *               according to the HID report descriptor and the HIDRAW kernel driver.
     * @throws IOException              if there is no currently connected Braille display.
     * @throws IllegalArgumentException if the buffer exceeds the maximum safe payload size for
     *                                  binder transactions of
     *                                  {@link IBinder#getSuggestedMaxIpcSizeBytes()}
     */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    void write(@NonNull byte[] buffer) throws IOException;

    /**
     * Disconnects from the currently connected Braille display.
     *
     * @see #isConnected()
     */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    void disconnect();

    /**
     * Provides test Braille display data to be used for automated CTS tests.
     *
     * <p>See {@code TEST_BRAILLE_DISPLAY_*} bundle keys.
     *
     * @hide
     */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)
    @TestApi
    static void setTestBrailleDisplayData(
            @NonNull AccessibilityService service,
            @NonNull List<Bundle> brailleDisplays) {
        checkApiFlagIsEnabled();
        final IAccessibilityServiceConnection serviceConnection =
                AccessibilityInteractionClient.getConnection(service.getConnectionId());
        if (serviceConnection != null) {
            try {
                serviceConnection.setTestBrailleDisplayData(brailleDisplays);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /** @hide */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    @TestApi
    String TEST_BRAILLE_DISPLAY_HIDRAW_PATH = "HIDRAW_PATH";
    /** @hide */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    @TestApi
    String TEST_BRAILLE_DISPLAY_DESCRIPTOR = "DESCRIPTOR";
    /** @hide */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    @TestApi
    String TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH = "BUS_BLUETOOTH";
    /** @hide */
    @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
    @TestApi
    String TEST_BRAILLE_DISPLAY_UNIQUE_ID = "UNIQUE_ID";
}
+267 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading