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