1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.accessibilityservice;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.FlaggedApi;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SuppressLint;
25 import android.annotation.TestApi;
26 import android.bluetooth.BluetoothDevice;
27 import android.hardware.usb.UsbDevice;
28 import android.os.Bundle;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.view.accessibility.AccessibilityInteractionClient;
32 import android.view.accessibility.Flags;
33 
34 import java.io.IOException;
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.List;
38 import java.util.concurrent.Executor;
39 
40 /**
41  * Used to communicate with a Braille display that supports the Braille display HID standard
42  * (usage page 0x41).
43  *
44  * <p>Only one Braille display may be connected at a time.
45  */
46 // This interface doesn't actually own resources. Its I/O connections are owned, monitored,
47 // and automatically closed by the system after the accessibility service is disconnected.
48 @SuppressLint("NotCloseable")
49 @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
50 public interface BrailleDisplayController {
51 
52     /**
53      * Throw {@link IllegalStateException} if this feature's aconfig flag is disabled.
54      *
55      * @hide
56      */
checkApiFlagIsEnabled()57     static void checkApiFlagIsEnabled() {
58         if (!Flags.brailleDisplayHid()) {
59             throw new IllegalStateException("Flag BRAILLE_DISPLAY_HID not enabled");
60         }
61     }
62 
63     /**
64      * Interface provided to {@link BrailleDisplayController} connection methods to
65      * receive callbacks from the system.
66      */
67     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
68     interface BrailleDisplayCallback {
69         /**
70          * The system cannot access connected HID devices.
71          */
72         @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
73         int FLAG_ERROR_CANNOT_ACCESS = 1 << 0;
74         /**
75          * A unique Braille display matching the requested properties could not be identified.
76          */
77         @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
78         int FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND = 1 << 1;
79 
80         /** @hide */
81         @Retention(RetentionPolicy.SOURCE)
82         @IntDef(flag = true, prefix = "FLAG_ERROR_", value = {
83                 FLAG_ERROR_CANNOT_ACCESS,
84                 FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND,
85         })
86         @interface ErrorCode {
87         }
88 
89         /**
90          * Callback to observe a successful Braille display connection.
91          *
92          * <p>The provided HID report descriptor should be used to understand the input bytes
93          * received from the Braille display via {@link #onInput} and to prepare
94          * the output sent to the Braille display via {@link #write}.
95          *
96          * @param hidDescriptor The HID report descriptor for this Braille display.
97          * @see #connect(BluetoothDevice, BrailleDisplayCallback)
98          * @see #connect(UsbDevice, BrailleDisplayCallback)
99          */
100         @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
onConnected(@onNull byte[] hidDescriptor)101         void onConnected(@NonNull byte[] hidDescriptor);
102 
103         /**
104          * Callback to observe a failed Braille display connection.
105          *
106          * @param errorFlags A bitmask of error codes for the connection failure.
107          * @see #connect(BluetoothDevice, BrailleDisplayCallback)
108          * @see #connect(UsbDevice, BrailleDisplayCallback)
109          */
110         @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
onConnectionFailed(@rrorCode int errorFlags)111         void onConnectionFailed(@ErrorCode int errorFlags);
112 
113         /**
114          * Callback to observe input bytes from the currently connected Braille display.
115          *
116          * @param input The input bytes from the Braille display, formatted according to the HID
117          *              report descriptor and the HIDRAW kernel driver.
118          */
119         @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
onInput(@onNull byte[] input)120         void onInput(@NonNull byte[] input);
121 
122         /**
123          * Callback to observe when the currently connected Braille display is disconnected by the
124          * system.
125          */
126         @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
onDisconnected()127         void onDisconnected();
128     }
129 
130     /**
131      * Connects to the requested bluetooth Braille display using the Braille
132      * display HID standard (usage page 0x41).
133      *
134      * <p>If successful then the HID report descriptor will be provided to
135      * {@link BrailleDisplayCallback#onConnected}
136      * and the Braille display will start sending incoming input bytes to
137      * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
138      * then the system will disconnect the Braille display.
139      *
140      * <p>Note that the callbacks will be executed on the main thread using
141      * {@link AccessibilityService#getMainExecutor()}. To specify the execution thread, use
142      * {@link #connect(BluetoothDevice, Executor, BrailleDisplayCallback)}.
143      *
144      * @param bluetoothDevice The Braille display device.
145      * @param callback        Callbacks used to provide connection results.
146      * @see BrailleDisplayCallback#onConnected
147      * @see BrailleDisplayCallback#onConnectionFailed
148      * @throws IllegalStateException if a Braille display is already connected to this controller.
149      */
150     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
151     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
connect(@onNull BluetoothDevice bluetoothDevice, @NonNull BrailleDisplayCallback callback)152     void connect(@NonNull BluetoothDevice bluetoothDevice,
153             @NonNull BrailleDisplayCallback callback);
154 
155     /**
156      * Connects to the requested bluetooth Braille display using the Braille
157      * display HID standard (usage page 0x41).
158      *
159      * <p>If successful then the HID report descriptor will be provided to
160      * {@link BrailleDisplayCallback#onConnected}
161      * and the Braille display will start sending incoming input bytes to
162      * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
163      * then the system will disconnect the Braille display.
164      *
165      * @param bluetoothDevice  The Braille display device.
166      * @param callbackExecutor Executor for executing the provided callbacks.
167      * @param callback         Callbacks used to provide connection results.
168      * @see BrailleDisplayCallback#onConnected
169      * @see BrailleDisplayCallback#onConnectionFailed
170      * @throws IllegalStateException if a Braille display is already connected to this controller.
171      */
172     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
173     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
connect(@onNull BluetoothDevice bluetoothDevice, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull BrailleDisplayCallback callback)174     void connect(@NonNull BluetoothDevice bluetoothDevice,
175             @NonNull @CallbackExecutor Executor callbackExecutor,
176             @NonNull BrailleDisplayCallback callback);
177 
178     /**
179      * Connects to the requested USB Braille display using the Braille
180      * display HID standard (usage page 0x41).
181      *
182      * <p>If successful then the HID report descriptor will be provided to
183      * {@link BrailleDisplayCallback#onConnected}
184      * and the Braille display will start sending incoming input bytes to
185      * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
186      * then the system will disconnect the Braille display.
187      *
188      * <p>The accessibility service app must already have approval to access the USB device
189      * from the standard {@link android.hardware.usb.UsbManager} access approval process.
190      *
191      * <p>Note that the callbacks will be executed on the main thread using
192      * {@link AccessibilityService#getMainExecutor()}. To specify the execution thread, use
193      * {@link #connect(UsbDevice, Executor, BrailleDisplayCallback)}.
194      *
195      * @param usbDevice        The Braille display device.
196      * @param callback         Callbacks used to provide connection results.
197      * @see BrailleDisplayCallback#onConnected
198      * @see BrailleDisplayCallback#onConnectionFailed
199      * @throws SecurityException if the caller does not have USB device approval.
200      * @throws IllegalStateException if a Braille display is already connected to this controller.
201      */
202     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
connect(@onNull UsbDevice usbDevice, @NonNull BrailleDisplayCallback callback)203     void connect(@NonNull UsbDevice usbDevice,
204             @NonNull BrailleDisplayCallback callback);
205 
206     /**
207      * Connects to the requested USB Braille display using the Braille
208      * display HID standard (usage page 0x41).
209      *
210      * <p>If successful then the HID report descriptor will be provided to
211      * {@link BrailleDisplayCallback#onConnected}
212      * and the Braille display will start sending incoming input bytes to
213      * {@link BrailleDisplayCallback#onInput}. If there is an error in reading input
214      * then the system will disconnect the Braille display.
215      *
216      * <p>The accessibility service app must already have approval to access the USB device
217      * from the standard {@link android.hardware.usb.UsbManager} access approval process.
218      *
219      * @param usbDevice        The Braille display device.
220      * @param callbackExecutor Executor for executing the provided callbacks.
221      * @param callback         Callbacks used to provide connection results.
222      * @see BrailleDisplayCallback#onConnected
223      * @see BrailleDisplayCallback#onConnectionFailed
224      * @throws SecurityException if the caller does not have USB device approval.
225      * @throws IllegalStateException if a Braille display is already connected to this controller.
226      */
227     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
connect(@onNull UsbDevice usbDevice, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull BrailleDisplayCallback callback)228     void connect(@NonNull UsbDevice usbDevice,
229             @NonNull @CallbackExecutor Executor callbackExecutor,
230             @NonNull BrailleDisplayCallback callback);
231 
232     /**
233      * Returns true if a Braille display is currently connected, otherwise false.
234      *
235      * @see #connect
236      */
237     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
isConnected()238     boolean isConnected();
239 
240     /**
241      * Writes a HID report to the currently connected Braille display.
242      *
243      * <p>This method returns immediately after dispatching the write request to the system.
244      * If the system experiences an error in writing output (e.g. the Braille display is unplugged
245      * after the system receives the write request but before writing the bytes to the Braille
246      * display) then the system will disconnect the Braille display, which calls
247      * {@link BrailleDisplayCallback#onDisconnected()}.
248      *
249      * @param buffer The bytes to write to the Braille display. These bytes should be formatted
250      *               according to the HID report descriptor and the HIDRAW kernel driver.
251      * @throws IOException              if there is no currently connected Braille display.
252      * @throws IllegalArgumentException if the buffer exceeds the maximum safe payload size for
253      *                                  binder transactions of
254      *                                  {@link IBinder#getSuggestedMaxIpcSizeBytes()}
255      */
256     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
write(@onNull byte[] buffer)257     void write(@NonNull byte[] buffer) throws IOException;
258 
259     /**
260      * Disconnects from the currently connected Braille display.
261      *
262      * @see #isConnected()
263      */
264     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
disconnect()265     void disconnect();
266 
267     /**
268      * Provides test Braille display data to be used for automated CTS tests.
269      *
270      * <p>See {@code TEST_BRAILLE_DISPLAY_*} bundle keys.
271      *
272      * @hide
273      */
274     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
275     @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)
276     @TestApi
setTestBrailleDisplayData( @onNull AccessibilityService service, @NonNull List<Bundle> brailleDisplays)277     static void setTestBrailleDisplayData(
278             @NonNull AccessibilityService service,
279             @NonNull List<Bundle> brailleDisplays) {
280         checkApiFlagIsEnabled();
281         final IAccessibilityServiceConnection serviceConnection =
282                 AccessibilityInteractionClient.getConnection(service.getConnectionId());
283         if (serviceConnection != null) {
284             try {
285                 serviceConnection.setTestBrailleDisplayData(brailleDisplays);
286             } catch (RemoteException e) {
287                 throw e.rethrowFromSystemServer();
288             }
289         }
290     }
291 
292     /** @hide */
293     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
294     @TestApi
295     String TEST_BRAILLE_DISPLAY_HIDRAW_PATH = "HIDRAW_PATH";
296     /** @hide */
297     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
298     @TestApi
299     String TEST_BRAILLE_DISPLAY_DESCRIPTOR = "DESCRIPTOR";
300     /** @hide */
301     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
302     @TestApi
303     String TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH = "BUS_BLUETOOTH";
304     /** @hide */
305     @FlaggedApi(Flags.FLAG_BRAILLE_DISPLAY_HID)
306     @TestApi
307     String TEST_BRAILLE_DISPLAY_UNIQUE_ID = "UNIQUE_ID";
308     /** @hide */
309     String TEST_BRAILLE_DISPLAY_NAME = "NAME";
310 }
311