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 com.android.server.accessibility;
18 
19 import static android.accessibilityservice.BrailleDisplayController.BrailleDisplayCallback.FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND;
20 import static android.accessibilityservice.BrailleDisplayController.BrailleDisplayCallback.FLAG_ERROR_CANNOT_ACCESS;
21 
22 import android.accessibilityservice.BrailleDisplayController;
23 import android.accessibilityservice.IBrailleDisplayConnection;
24 import android.accessibilityservice.IBrailleDisplayController;
25 import android.annotation.IntDef;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.annotation.PermissionManuallyEnforced;
29 import android.annotation.RequiresNoPermission;
30 import android.bluetooth.BluetoothDevice;
31 import android.hardware.usb.UsbDevice;
32 import android.os.Bundle;
33 import android.os.HandlerThread;
34 import android.os.IBinder;
35 import android.os.Process;
36 import android.os.RemoteException;
37 import android.text.TextUtils;
38 import android.util.ArrayMap;
39 import android.util.ArraySet;
40 import android.util.Pair;
41 import android.util.Slog;
42 
43 import com.android.internal.annotations.VisibleForTesting;
44 
45 import java.io.File;
46 import java.io.FileInputStream;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.OutputStream;
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 import java.nio.file.DirectoryStream;
54 import java.nio.file.Files;
55 import java.nio.file.Path;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Collection;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Objects;
62 import java.util.Set;
63 import java.util.function.Function;
64 
65 /**
66  * This class represents the connection between {@code system_server} and a connected
67  * Braille Display using the Braille Display HID standard (usage page 0x41).
68  */
69 class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub {
70     private static final String LOG_TAG = "BrailleDisplayConnection";
71 
72     /**
73      * Represents the connection type of a Braille display.
74      *
75      * <p>The integer values must match the kernel's bus type values because this bus type is
76      * used to locate the correct HIDRAW node using data from the kernel. These values come
77      * from the UAPI header file bionic/libc/kernel/uapi/linux/input.h, which is guaranteed
78      * to stay constant.
79      */
80     @Retention(RetentionPolicy.SOURCE)
81     @IntDef(flag = true, prefix = {"BUS_"}, value = {
82             BUS_UNKNOWN,
83             BUS_USB,
84             BUS_BLUETOOTH
85     })
86     @interface BusType {
87     }
88     static final int BUS_UNKNOWN = -1;
89     static final int BUS_USB = 0x03;
90     static final int BUS_BLUETOOTH = 0x05;
91 
92     // Access to this static object must be guarded by a lock that is shared for all instances
93     // of this class: the singular Accessibility system_server lock (mLock).
94     private static final Set<File> sConnectedNodes = new ArraySet<>();
95 
96     // Used to guard to AIDL methods from concurrent calls.
97     // Lock must match the one used by AccessibilityServiceConnection, which itself
98     // comes from AccessibilityManagerService.
99     private final Object mLock;
100     private final AccessibilityServiceConnection mServiceConnection;
101 
102 
103     private File mHidrawNode;
104     private IBrailleDisplayController mController;
105 
106     private Thread mInputThread;
107     private OutputStream mOutputStream;
108     private HandlerThread mOutputThread;
109 
110     // mScanner is not final because tests may modify this to use a test-only scanner.
111     private BrailleDisplayScanner mScanner;
112 
BrailleDisplayConnection(@onNull Object lock, @NonNull AccessibilityServiceConnection serviceConnection)113     BrailleDisplayConnection(@NonNull Object lock,
114             @NonNull AccessibilityServiceConnection serviceConnection) {
115         this.mLock = Objects.requireNonNull(lock);
116         this.mScanner = getDefaultNativeScanner(new DefaultNativeInterface());
117         this.mServiceConnection = Objects.requireNonNull(serviceConnection);
118     }
119 
120     /**
121      * Used for `cmd accessibility` to check hidraw access.
122      */
createScannerForShell()123     static BrailleDisplayScanner createScannerForShell() {
124         return getDefaultNativeScanner(new DefaultNativeInterface());
125     }
126 
127     /**
128      * Interface to scan for properties of connected Braille displays.
129      *
130      * <p>Helps simplify testing Braille Display APIs using test data without requiring
131      * a real Braille display to be connected to the device, by using a test implementation
132      * of this interface.
133      *
134      * @see #getDefaultNativeScanner
135      * @see #setTestData
136      */
137     interface BrailleDisplayScanner {
getHidrawNodePaths(@onNull Path directory)138         Collection<Path> getHidrawNodePaths(@NonNull Path directory);
139 
getDeviceReportDescriptor(@onNull Path path)140         byte[] getDeviceReportDescriptor(@NonNull Path path);
141 
getUniqueId(@onNull Path path)142         String getUniqueId(@NonNull Path path);
143 
144         @BusType
getDeviceBusType(@onNull Path path)145         int getDeviceBusType(@NonNull Path path);
146 
getName(@onNull Path path)147         String getName(@NonNull Path path);
148     }
149 
150     /**
151      * Finds the Braille display HIDRAW node associated with the provided unique ID.
152      *
153      * <p>If found, saves instance state for this connection and starts a thread to
154      * read from the Braille display.
155      *
156      * @param expectedUniqueId The expected unique ID of the device to connect, from
157      *                         {@link UsbDevice#getSerialNumber()} or
158      *                         {@link BluetoothDevice#getAddress()}.
159      * @param expectedName     The expected name of the device to connect, from
160      *                         {@link BluetoothDevice#getName()} or
161      *                         {@link UsbDevice#getProductName()}.
162      * @param expectedBusType  The expected bus type from {@link BusType}.
163      * @param controller       Interface containing oneway callbacks used to communicate with the
164      *                         {@link android.accessibilityservice.BrailleDisplayController}.
165      */
connectLocked( @onNull String expectedUniqueId, @Nullable String expectedName, @BusType int expectedBusType, @NonNull IBrailleDisplayController controller)166     void connectLocked(
167             @NonNull String expectedUniqueId,
168             @Nullable String expectedName,
169             @BusType int expectedBusType,
170             @NonNull IBrailleDisplayController controller) {
171         Objects.requireNonNull(expectedUniqueId);
172         this.mController = Objects.requireNonNull(controller);
173 
174         final Path devicePath = Path.of("/dev");
175         final List<Pair<File, byte[]>> result = new ArrayList<>();
176         final Collection<Path> hidrawNodePaths = mScanner.getHidrawNodePaths(devicePath);
177         if (hidrawNodePaths == null) {
178             Slog.w(LOG_TAG, "Unable to access the HIDRAW node directory");
179             sendConnectionErrorLocked(FLAG_ERROR_CANNOT_ACCESS);
180             return;
181         }
182         boolean unableToGetDescriptor = false;
183         // For every present HIDRAW device node:
184         for (Path path : hidrawNodePaths) {
185             final byte[] descriptor = mScanner.getDeviceReportDescriptor(path);
186             if (descriptor == null) {
187                 unableToGetDescriptor = true;
188                 continue;
189             }
190             final boolean matchesIdentifier;
191             final String uniqueId = mScanner.getUniqueId(path);
192             if (uniqueId != null) {
193                 matchesIdentifier = expectedUniqueId.equalsIgnoreCase(uniqueId);
194             } else {
195                 // HIDIOCGRAWUNIQ was added in kernel version 5.7.
196                 // If the device has an older kernel that does not support that ioctl then as a
197                 // fallback we can check against the device name (from HIDIOCGRAWNAME).
198                 final String name = mScanner.getName(path);
199                 matchesIdentifier = !TextUtils.isEmpty(expectedName) && expectedName.equals(name);
200             }
201             if (isBrailleDisplay(descriptor)
202                     && mScanner.getDeviceBusType(path) == expectedBusType
203                     && matchesIdentifier) {
204                 result.add(Pair.create(path.toFile(), descriptor));
205             }
206         }
207 
208         // Return success only when exactly one matching device node is found.
209         if (result.size() != 1) {
210             @BrailleDisplayController.BrailleDisplayCallback.ErrorCode int errorCode =
211                     FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND;
212             // If we were unable to get some /dev/hidraw* descriptor then tell the accessibility
213             // service that the device may not have proper access to these device nodes.
214             if (unableToGetDescriptor) {
215                 Slog.w(LOG_TAG, "Unable to access some HIDRAW node's descriptor");
216                 errorCode |= FLAG_ERROR_CANNOT_ACCESS;
217             } else {
218                 Slog.w(LOG_TAG,
219                         "Unable to find a unique Braille display matching the provided device");
220             }
221             sendConnectionErrorLocked(errorCode);
222             return;
223         }
224 
225         this.mHidrawNode = result.get(0).first;
226         final byte[] reportDescriptor = result.get(0).second;
227 
228         // Only one connection instance should exist for this hidraw node, across
229         // all currently running accessibility services.
230         if (sConnectedNodes.contains(this.mHidrawNode)) {
231             Slog.w(LOG_TAG,
232                     "Unable to find an unused Braille display matching the provided device");
233             sendConnectionErrorLocked(FLAG_ERROR_BRAILLE_DISPLAY_NOT_FOUND);
234             return;
235         }
236         sConnectedNodes.add(this.mHidrawNode);
237 
238         startReadingLocked();
239 
240         try {
241             mServiceConnection.onBrailleDisplayConnectedLocked(this);
242             mController.onConnected(this, reportDescriptor);
243         } catch (RemoteException e) {
244             Slog.e(LOG_TAG, "Error calling onConnected", e);
245             disconnect();
246         }
247     }
248 
sendConnectionErrorLocked( @railleDisplayController.BrailleDisplayCallback.ErrorCode int errorCode)249     private void sendConnectionErrorLocked(
250             @BrailleDisplayController.BrailleDisplayCallback.ErrorCode int errorCode) {
251         try {
252             mController.onConnectionFailed(errorCode);
253         } catch (RemoteException e) {
254             Slog.e(LOG_TAG, "Error calling onConnectionFailed", e);
255         }
256     }
257 
258     /** Returns true if this descriptor includes usages for the Braille display usage page 0x41. */
259     @VisibleForTesting
isBrailleDisplay(byte[] descriptor)260     static boolean isBrailleDisplay(byte[] descriptor) {
261         boolean foundMatch = false;
262         for (int i = 0; i < descriptor.length; i++) {
263             // HID Spec "6.2.2.2 Short Items" defines that the report descriptor is a collection of
264             // items: each item is a collection of bytes where the first byte defines info about
265             // the type of item and the following 0, 1, 2, or 4 bytes are data bytes for that item.
266             // All items in the HID descriptor are expected to be Short Items.
267             final byte itemInfo = descriptor[i];
268             if (!isHidItemShort(itemInfo)) {
269                 Slog.w(LOG_TAG, "Item " + itemInfo + " declares unsupported long type");
270                 return false;
271             }
272             final int dataSize = getHidItemDataSize(itemInfo);
273             if (i + dataSize >= descriptor.length) {
274                 Slog.w(LOG_TAG, "Item " + itemInfo + " specifies size past the remaining bytes");
275                 return false;
276             }
277             // The item we're looking for (usage page declaration) should have size 1.
278             if (dataSize == 1) {
279                 final byte itemData = descriptor[i + 1];
280                 if (isHidItemBrailleDisplayUsagePage(itemInfo, itemData)) {
281                     foundMatch = true;
282                 }
283             }
284             // Move to the next item by skipping past all data bytes in this item.
285             i += dataSize;
286         }
287         return foundMatch;
288     }
289 
isHidItemShort(byte itemInfo)290     private static boolean isHidItemShort(byte itemInfo) {
291         // Info bits 7-4 describe the item type, and HID Spec "6.2.2.3 Long Items" says that long
292         // items always have type bits 1111. Otherwise, the item is a short item.
293         return (itemInfo & 0b1111_0000) != 0b1111_0000;
294     }
295 
getHidItemDataSize(byte itemInfo)296     private static int getHidItemDataSize(byte itemInfo) {
297         // HID Spec "6.2.2.2 Short Items" says that info bits 0-1 specify the optional data size:
298         // 0, 1, 2, or 4 bytes.
299         return switch (itemInfo & 0b0000_0011) {
300             case 0b00 -> 0;
301             case 0b01 -> 1;
302             case 0b10 -> 2;
303             default -> 4;
304         };
305     }
306 
isHidItemBrailleDisplayUsagePage(byte itemInfo, byte itemData)307     private static boolean isHidItemBrailleDisplayUsagePage(byte itemInfo, byte itemData) {
308         // From HID Spec "6.2.2.7 Global Items"
309         final byte usagePageType = 0b0000_0100;
310         // From HID Usage Tables version 1.2.
311         final byte brailleDisplayUsagePage = 0x41;
312         // HID Spec "6.2.2.2 Short Items" says item info bits 2-7 describe the type and
313         // function of the item.
314         final byte itemType = (byte) (itemInfo & 0b1111_1100);
315         return itemType == usagePageType && itemData == brailleDisplayUsagePage;
316     }
317 
318     /**
319      * Checks that the AccessibilityService that owns this BrailleDisplayConnection
320      * is still connected to the system.
321      *
322      * @throws IllegalStateException if not connected
323      */
assertServiceIsConnectedLocked()324     private void assertServiceIsConnectedLocked() {
325         if (!mServiceConnection.isConnectedLocked()) {
326             throw new IllegalStateException("Accessibility service is not connected");
327         }
328     }
329 
330     /**
331      * Disconnects from this Braille display. This object is no longer valid after
332      * this call returns.
333      */
334     @Override
335     // This is a cleanup method, so allow the call even if the calling service was disabled.
336     @RequiresNoPermission
disconnect()337     public void disconnect() {
338         synchronized (mLock) {
339             closeInputLocked();
340             closeOutputLocked();
341             mServiceConnection.onBrailleDisplayDisconnectedLocked();
342             try {
343                 mController.onDisconnected();
344             } catch (RemoteException e) {
345                 Slog.e(LOG_TAG, "Error calling onDisconnected");
346             }
347             sConnectedNodes.remove(this.mHidrawNode);
348         }
349     }
350 
351     /**
352      * Writes the provided HID bytes to this Braille display.
353      *
354      * <p>Writes are posted to a background thread handler.
355      *
356      * @param buffer The bytes to write to the Braille display. These bytes should be formatted
357      *               according to the report descriptor.
358      */
359     @Override
360     @PermissionManuallyEnforced // by assertServiceIsConnectedLocked()
write(@onNull byte[] buffer)361     public void write(@NonNull byte[] buffer) {
362         Objects.requireNonNull(buffer);
363         if (buffer.length > IBinder.getSuggestedMaxIpcSizeBytes()) {
364             Slog.e(LOG_TAG, "Requested write of size " + buffer.length
365                     + " which is larger than maximum " + IBinder.getSuggestedMaxIpcSizeBytes());
366             // The caller only got here by bypassing the AccessibilityService-side check with
367             // reflection, so disconnect this connection to prevent further attempts.
368             disconnect();
369             return;
370         }
371         synchronized (mLock) {
372             assertServiceIsConnectedLocked();
373             if (mOutputThread == null) {
374                 try {
375                     mOutputStream = new FileOutputStream(mHidrawNode);
376                 } catch (Exception e) {
377                     Slog.e(LOG_TAG, "Unable to create write stream", e);
378                     disconnect();
379                     return;
380                 }
381                 mOutputThread = new HandlerThread("BrailleDisplayConnection output thread",
382                         Process.THREAD_PRIORITY_BACKGROUND);
383                 mOutputThread.setDaemon(true);
384                 mOutputThread.start();
385             }
386             // TODO: b/316035785 - Proactively disconnect a misbehaving Braille display by calling
387             //  disconnect() if the mOutputThread handler queue grows too large.
388             mOutputThread.getThreadHandler().post(() -> {
389                 try {
390                     mOutputStream.write(buffer);
391                 } catch (IOException e) {
392                     Slog.d(LOG_TAG, "Error writing to connected Braille display", e);
393                     disconnect();
394                 }
395             });
396         }
397     }
398 
399     /**
400      * Starts reading HID bytes from this Braille display.
401      *
402      * <p>Reads are performed on a background thread.
403      */
startReadingLocked()404     private void startReadingLocked() {
405         mInputThread = new Thread(() -> {
406             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
407             try (InputStream inputStream = new FileInputStream(mHidrawNode)) {
408                 final byte[] buffer = new byte[IBinder.getSuggestedMaxIpcSizeBytes()];
409                 int readSize;
410                 while (!Thread.interrupted()) {
411                     if (!mHidrawNode.exists()) {
412                         disconnect();
413                         break;
414                     }
415                     // Reading from the HIDRAW character device node will block
416                     // until bytes are available.
417                     readSize = inputStream.read(buffer);
418                     if (readSize > 0) {
419                         try {
420                             // Send the input to the AccessibilityService.
421                             mController.onInput(Arrays.copyOfRange(buffer, 0, readSize));
422                         } catch (RemoteException e) {
423                             // Error communicating with the AccessibilityService.
424                             Slog.e(LOG_TAG, "Error calling onInput", e);
425                             disconnect();
426                             break;
427                         }
428                     }
429                 }
430             } catch (IOException e) {
431                 Slog.d(LOG_TAG, "Error reading from connected Braille display", e);
432                 disconnect();
433             }
434         }, "BrailleDisplayConnection input thread");
435         mInputThread.setDaemon(true);
436         mInputThread.start();
437     }
438 
439     /** Stop the Input thread. */
closeInputLocked()440     private void closeInputLocked() {
441         if (mInputThread != null) {
442             mInputThread.interrupt();
443         }
444         mInputThread = null;
445     }
446 
447     /** Stop the Output thread and close the Output stream. */
closeOutputLocked()448     private void closeOutputLocked() {
449         if (mOutputThread != null) {
450             mOutputThread.quit();
451         }
452         mOutputThread = null;
453         if (mOutputStream != null) {
454             try {
455                 mOutputStream.close();
456             } catch (IOException e) {
457                 Slog.e(LOG_TAG, "Unable to close output stream", e);
458             }
459         }
460         mOutputStream = null;
461     }
462 
463     /**
464      * Returns a {@link BrailleDisplayScanner} that opens {@link FileInputStream}s to read
465      * from HIDRAW nodes and perform ioctls using the provided {@link NativeInterface}.
466      */
467     @VisibleForTesting
getDefaultNativeScanner(@onNull NativeInterface nativeInterface)468     static BrailleDisplayScanner getDefaultNativeScanner(@NonNull NativeInterface nativeInterface) {
469         Objects.requireNonNull(nativeInterface);
470         return new BrailleDisplayScanner() {
471             private static final String HIDRAW_DEVICE_GLOB = "hidraw*";
472 
473             @Override
474             public Collection<Path> getHidrawNodePaths(@NonNull Path directory) {
475                 final List<Path> result = new ArrayList<>();
476                 try (DirectoryStream<Path> hidrawNodePaths = Files.newDirectoryStream(
477                         directory, HIDRAW_DEVICE_GLOB)) {
478                     for (Path path : hidrawNodePaths) {
479                         result.add(path);
480                     }
481                     return result;
482                 } catch (IOException e) {
483                     return null;
484                 }
485             }
486 
487             private <T> T readFromFileDescriptor(Path path, Function<Integer, T> readFn) {
488                 try (FileInputStream stream = new FileInputStream(path.toFile())) {
489                     return readFn.apply(stream.getFD().getInt$());
490                 } catch (IOException e) {
491                     return null;
492                 }
493             }
494 
495             @Override
496             public byte[] getDeviceReportDescriptor(@NonNull Path path) {
497                 Objects.requireNonNull(path);
498                 return readFromFileDescriptor(path, fd -> {
499                     final int descSize = nativeInterface.getHidrawDescSize(fd);
500                     if (descSize > 0) {
501                         return nativeInterface.getHidrawDesc(fd, descSize);
502                     }
503                     return null;
504                 });
505             }
506 
507             @Override
508             public String getUniqueId(@NonNull Path path) {
509                 Objects.requireNonNull(path);
510                 return readFromFileDescriptor(path, nativeInterface::getHidrawUniq);
511             }
512 
513             @Override
514             public int getDeviceBusType(@NonNull Path path) {
515                 Objects.requireNonNull(path);
516                 Integer busType = readFromFileDescriptor(path, nativeInterface::getHidrawBusType);
517                 return busType != null ? busType : BUS_UNKNOWN;
518             }
519 
520             @Override
521             public String getName(@NonNull Path path) {
522                 Objects.requireNonNull(path);
523                 return readFromFileDescriptor(path, nativeInterface::getHidrawName);
524             }
525         };
526     }
527 
528     /**
529      * Sets test data to be used by CTS tests.
530      *
531      * <p>Replaces the default {@link BrailleDisplayScanner} object for this connection,
532      * and also returns it to allow unit testing this test-only implementation.
533      *
534      * @see BrailleDisplayController#setTestBrailleDisplayData
535      */
536     BrailleDisplayScanner setTestData(@NonNull List<Bundle> brailleDisplays) {
537         Objects.requireNonNull(brailleDisplays);
538         final Map<Path, Bundle> brailleDisplayMap = new ArrayMap<>();
539         for (Bundle brailleDisplay : brailleDisplays) {
540             Path hidrawNodePath = Path.of(brailleDisplay.getString(
541                     BrailleDisplayController.TEST_BRAILLE_DISPLAY_HIDRAW_PATH));
542             brailleDisplayMap.put(hidrawNodePath, brailleDisplay);
543         }
544         synchronized (mLock) {
545             mScanner = new BrailleDisplayScanner() {
546                 @Override
547                 public Collection<Path> getHidrawNodePaths(@NonNull Path directory) {
548                     return brailleDisplayMap.isEmpty() ? null : brailleDisplayMap.keySet();
549                 }
550 
551                 @Override
552                 public byte[] getDeviceReportDescriptor(@NonNull Path path) {
553                     return brailleDisplayMap.get(path).getByteArray(
554                             BrailleDisplayController.TEST_BRAILLE_DISPLAY_DESCRIPTOR);
555                 }
556 
557                 @Override
558                 public String getUniqueId(@NonNull Path path) {
559                     return brailleDisplayMap.get(path).getString(
560                             BrailleDisplayController.TEST_BRAILLE_DISPLAY_UNIQUE_ID);
561                 }
562 
563                 @Override
564                 public int getDeviceBusType(@NonNull Path path) {
565                     return brailleDisplayMap.get(path).getBoolean(
566                             BrailleDisplayController.TEST_BRAILLE_DISPLAY_BUS_BLUETOOTH)
567                             ? BUS_BLUETOOTH : BUS_USB;
568                 }
569 
570                 @Override
571                 public String getName(@NonNull Path path) {
572                     return brailleDisplayMap.get(path).getString(
573                             BrailleDisplayController.TEST_BRAILLE_DISPLAY_NAME);
574                 }
575             };
576             return mScanner;
577         }
578     }
579 
580     /**
581      * This interface exists to support unit testing {@link #getDefaultNativeScanner}.
582      */
583     @VisibleForTesting
584     interface NativeInterface {
585         /**
586          * Returns the HIDRAW descriptor size for the file descriptor.
587          *
588          * @return the result of ioctl(HIDIOCGRDESCSIZE), or -1 if the ioctl fails.
589          */
590         int getHidrawDescSize(int fd);
591 
592         /**
593          * Returns the HIDRAW descriptor for the file descriptor.
594          *
595          * @return the result of ioctl(HIDIOCGRDESC), or null if the ioctl fails.
596          */
597         byte[] getHidrawDesc(int fd, int descSize);
598 
599         /**
600          * Returns the HIDRAW unique identifier for the file descriptor.
601          *
602          * @return the result of ioctl(HIDIOCGRAWUNIQ), or null if the ioctl fails.
603          */
604         String getHidrawUniq(int fd);
605 
606         /**
607          * Returns the HIDRAW bus type for the file descriptor.
608          *
609          * @return the result of ioctl(HIDIOCGRAWINFO).bustype, or -1 if the ioctl fails.
610          */
611         int getHidrawBusType(int fd);
612 
613         String getHidrawName(int fd);
614     }
615 
616     /** Native interface that actually calls native HIDRAW ioctls. */
617     private static class DefaultNativeInterface implements NativeInterface {
618         @Override
619         public int getHidrawDescSize(int fd) {
620             return nativeGetHidrawDescSize(fd);
621         }
622 
623         @Override
624         public byte[] getHidrawDesc(int fd, int descSize) {
625             return nativeGetHidrawDesc(fd, descSize);
626         }
627 
628         @Override
629         public String getHidrawUniq(int fd) {
630             return nativeGetHidrawUniq(fd);
631         }
632 
633         @Override
634         public int getHidrawBusType(int fd) {
635             return nativeGetHidrawBusType(fd);
636         }
637 
638         @Override
639         public String getHidrawName(int fd) {
640             return nativeGetHidrawName(fd);
641         }
642     }
643 
644     private static native int nativeGetHidrawDescSize(int fd);
645 
646     private static native byte[] nativeGetHidrawDesc(int fd, int descSize);
647 
648     private static native String nativeGetHidrawUniq(int fd);
649 
650     private static native int nativeGetHidrawBusType(int fd);
651 
652     private static native String nativeGetHidrawName(int fd);
653 }
654