/* * BRLTTY - A background process providing access to the console screen (when in * text mode) for a blind person using a refreshable braille display. * * Copyright (C) 1995-2018 by The BRLTTY Developers. * * BRLTTY comes with ABSOLUTELY NO WARRANTY. * * This is free software, placed under the terms of the * GNU Lesser General Public License, as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any * later version. Please see the file LICENSE-LGPL for details. * * Web Page: http://brltty.com/ * * This software is maintained by Dave Mielke . */ #include "prologue.h" #include #include #include #include "log.h" #include "queue.h" #include "io_usb.h" #include "usb_internal.h" #include "system_java.h" typedef struct { JNIEnv *env; jobject device; UsbDeviceDescriptor descriptor; } UsbHostDevice; static Queue *usbHostDevices = NULL; struct UsbDeviceExtensionStruct { const UsbHostDevice *host; jobject connection; jobject interface; }; struct UsbEndpointExtensionStruct { UsbEndpoint *endpoint; jobject object; }; static jclass usbHelperClass = NULL; static jclass usbDeviceClass = NULL; static jclass usbInterfaceClass = NULL; static jclass usbConnectionClass = NULL; static int usbFindHelperClass (JNIEnv *env) { return findJavaClass(env, &usbHelperClass, "org/a11y/brltty/android/UsbHelper"); } static jobject usbGetDeviceIterator (JNIEnv *env) { if (usbFindHelperClass(env)) { static jmethodID method = 0; if (findJavaStaticMethod(env, &method, usbHelperClass, "getDeviceIterator", JAVA_SIG_METHOD(JAVA_SIG_OBJECT(java/util/Iterator), ))) { jobject iterator = (*env)->CallStaticObjectMethod(env, usbHelperClass, method); if (iterator) return iterator; clearJavaException(env, 1); errno = EIO; } } return NULL; } static jobject usbGetNextDevice (JNIEnv *env, jobject iterator) { if (usbFindHelperClass(env)) { static jmethodID method = 0; if (findJavaStaticMethod(env, &method, usbHelperClass, "getNextDevice", JAVA_SIG_METHOD(JAVA_SIG_OBJECT(android/hardware/usb/UsbDevice), JAVA_SIG_OBJECT(java/util/Iterator)))) { jobject device = (*env)->CallStaticObjectMethod(env, usbHelperClass, method, iterator); if (device) return device; clearJavaException(env, 1); } } return NULL; } static jobject usbGetDeviceInterface (JNIEnv *env, jobject device, jint identifier) { if (usbFindHelperClass(env)) { static jmethodID method = 0; if (findJavaStaticMethod(env, &method, usbHelperClass, "getDeviceInterface", JAVA_SIG_METHOD(JAVA_SIG_OBJECT(android/hardware/usb/UsbInterface), JAVA_SIG_OBJECT(android/hardware/usb/UsbDevice) // device JAVA_SIG_INT // identifier ))) { jobject interface = (*env)->CallStaticObjectMethod(env, usbHelperClass, method, device, identifier); if (interface) return interface; clearJavaException(env, 1); errno = EIO; } } return NULL; } static jobject usbGetInterfaceEndpoint (JNIEnv *env, jobject interface, jint address) { if (usbFindHelperClass(env)) { static jmethodID method = 0; if (findJavaStaticMethod(env, &method, usbHelperClass, "getInterfaceEndpoint", JAVA_SIG_METHOD(JAVA_SIG_OBJECT(android/hardware/usb/UsbEndpoint), JAVA_SIG_OBJECT(android/hardware/usb/UsbInterface) // interface JAVA_SIG_INT // address ))) { jobject endpoint = (*env)->CallStaticObjectMethod(env, usbHelperClass, method, interface, address); if (endpoint) return endpoint; clearJavaException(env, 1); errno = EIO; } } return NULL; } static jobject usbOpenDeviceConnection (JNIEnv *env, jobject device) { logMessage(LOG_CATEGORY(USB_IO), "opening device connection"); if (usbFindHelperClass(env)) { static jmethodID method = 0; if (findJavaStaticMethod(env, &method, usbHelperClass, "openDeviceConnection", JAVA_SIG_METHOD(JAVA_SIG_OBJECT(android/hardware/usb/UsbDeviceConnection), JAVA_SIG_OBJECT(android/hardware/usb/UsbDevice)))) { jobject connection = (*env)->CallStaticObjectMethod(env, usbHelperClass, method, device); if (connection) return connection; clearJavaException(env, 1); errno = EIO; } } return NULL; } static int usbFindDeviceClass (JNIEnv *env) { return findJavaClass(env, &usbDeviceClass, "android/hardware/usb/UsbDevice"); } static int usbGetIntDeviceProperty ( JNIEnv *env, jint *value, jobject device, const char *methodName, jmethodID *methodIdentifier ) { if (usbFindDeviceClass(env)) { if (findJavaInstanceMethod(env, methodIdentifier, usbDeviceClass, methodName, JAVA_SIG_METHOD(JAVA_SIG_INT, ))) { if (!clearJavaException(env, 1)) { *value = (*env)->CallIntMethod(env, device, *methodIdentifier); return 1; } errno = EIO; } } return 0; } static int usbGetDeviceVendor (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) { static jmethodID method = 0; jint vendor; int ok = usbGetIntDeviceProperty(env, &vendor, device, "getVendorId", &method); if (ok) putLittleEndian16(&descriptor->idVendor, vendor); return ok; } static int usbGetDeviceProduct (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) { static jmethodID method = 0; jint product; int ok = usbGetIntDeviceProperty(env, &product, device, "getProductId", &method); if (ok) putLittleEndian16(&descriptor->idProduct, product); return ok; } static int usbGetDeviceClass (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) { static jmethodID method = 0; jint class; int ok = usbGetIntDeviceProperty(env, &class, device, "getDeviceClass", &method); if (ok) descriptor->bDeviceClass = class; return ok; } static int usbGetDeviceSubclass (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) { static jmethodID method = 0; jint subclass; int ok = usbGetIntDeviceProperty(env, &subclass, device, "getDeviceSubclass", &method); if (ok) descriptor->bDeviceSubClass = subclass; return ok; } static int usbGetDeviceProtocol (JNIEnv *env, jobject device, UsbDeviceDescriptor *descriptor) { static jmethodID method = 0; jint protocol; int ok = usbGetIntDeviceProperty(env, &protocol, device, "getDeviceProtocol", &method); if (ok) descriptor->bDeviceProtocol = protocol; return ok; } static int usbFindInterfaceClass (JNIEnv *env) { return findJavaClass(env, &usbInterfaceClass, "android/hardware/usb/UsbInterface"); } static int usbGetInterfaceIdentifier (JNIEnv *env, uint8_t *identifier, jobject interface) { if (usbFindInterfaceClass(env)) { static jmethodID method = 0; if (findJavaInstanceMethod(env, &method, usbInterfaceClass, "getId", JAVA_SIG_METHOD(JAVA_SIG_INT, ))) { jint result = (*env)->CallIntMethod(env, interface, method); if (!clearJavaException(env, 1)) { *identifier = result; return 1; } errno = EIO; } } return 0; } static int usbFindConnectionClass (JNIEnv *env) { return findJavaClass(env, &usbConnectionClass, "android/hardware/usb/UsbDeviceConnection"); } static int usbDoClaimInterface (JNIEnv *env, jobject connection, jobject interface) { if (usbFindConnectionClass(env)) { static jmethodID method = 0; if (findJavaInstanceMethod(env, &method, usbConnectionClass, "claimInterface", JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN, JAVA_SIG_OBJECT(android/hardware/usb/UsbInterface) // interface JAVA_SIG_BOOLEAN // force ))) { jboolean result = (*env)->CallBooleanMethod(env, connection, method, interface, JNI_TRUE); if (clearJavaException(env, 1)) { errno = EIO; } else if (result) { return 1; } else { logSystemError("USB claim interface"); } } } return 0; } static int usbDoReleaseInterface (JNIEnv *env, jobject connection, jobject interface) { if (usbFindConnectionClass(env)) { static jmethodID method = 0; if (findJavaInstanceMethod(env, &method, usbConnectionClass, "releaseInterface", JAVA_SIG_METHOD(JAVA_SIG_BOOLEAN, JAVA_SIG_OBJECT(android/hardware/usb/UsbInterface) // interface ))) { jboolean result = (*env)->CallBooleanMethod(env, connection, method, interface); if (clearJavaException(env, 1)) { errno = EIO; } else if (result) { return 1; } else { logSystemError("USB release interface"); } } } return 0; } static int usbDoControlTransfer ( JNIEnv *env, jobject connection, int type, int request, int value, int index, jbyteArray buffer, int length, int timeout ) { if (usbFindConnectionClass(env)) { static jmethodID method = 0; if (findJavaInstanceMethod(env, &method, usbConnectionClass, "controlTransfer", JAVA_SIG_METHOD(JAVA_SIG_INT, JAVA_SIG_INT // type JAVA_SIG_INT // request JAVA_SIG_INT // value JAVA_SIG_INT // index JAVA_SIG_ARRAY(JAVA_SIG_BYTE) // buffer JAVA_SIG_INT // length JAVA_SIG_INT // timeout ))) { jint result = (*env)->CallIntMethod(env, connection, method, type, request, value, index, buffer, length, timeout); if (!clearJavaException(env, 1)) return result; errno = EIO; } } return -1; } static int usbDoBulkTransfer ( JNIEnv *env, jobject connection, jobject endpoint, jbyteArray buffer, int length, int timeout ) { if (usbFindConnectionClass(env)) { static jmethodID method = 0; if (findJavaInstanceMethod(env, &method, usbConnectionClass, "bulkTransfer", JAVA_SIG_METHOD(JAVA_SIG_INT, JAVA_SIG_OBJECT(android/hardware/usb/UsbEndpoint) // endpoint JAVA_SIG_ARRAY(JAVA_SIG_BYTE) // buffer JAVA_SIG_INT // length JAVA_SIG_INT // timeout ))) { jint result = (*env)->CallIntMethod(env, connection, method, endpoint, buffer, length, timeout); if (!clearJavaException(env, 1)) return result; errno = EIO; } } return -1; } static void usbCloseDeviceConnection (JNIEnv *env, jobject connection) { if (usbFindConnectionClass(env)) { static jmethodID method = 0; if (findJavaInstanceMethod(env, &method, usbConnectionClass, "close", JAVA_SIG_METHOD(JAVA_SIG_VOID, ))) { (*env)->CallVoidMethod(env, connection, method); clearJavaException(env, 1); } } } static int usbOpenConnection (UsbDeviceExtension *devx) { if (devx->connection) return 1; { const UsbHostDevice *host = devx->host; jobject connection = usbOpenDeviceConnection(host->env, host->device); if (connection) { devx->connection = (*host->env)->NewGlobalRef(host->env, connection); (*host->env)->DeleteLocalRef(host->env, connection); connection = NULL; if (devx->connection) return 1; logMallocError(); clearJavaException(host->env, 0); } } return 0; } static void usbUnsetInterface (UsbDeviceExtension *devx) { if (devx->interface) { JNIEnv *env = devx->host->env; (*env)->DeleteGlobalRef(env, devx->interface); devx->interface = NULL; } } static int usbSetInterface (UsbDeviceExtension *devx, uint8_t identifier) { JNIEnv *env = devx->host->env; if (devx->interface) { uint8_t id; if (!usbGetInterfaceIdentifier(env, &id, devx->interface)) return 0; if (id == identifier) return 1; } { jobject interface = usbGetDeviceInterface(env, devx->host->device, identifier); if (interface) { usbUnsetInterface(devx); devx->interface = (*env)->NewGlobalRef(env, interface); (*env)->DeleteLocalRef(env, interface); interface = NULL; if (devx->interface) return 1; logMallocError(); } } return 0; } int usbDisableAutosuspend (UsbDevice *device) { logUnsupportedFunction(); return 0; } int usbSetConfiguration (UsbDevice *device, unsigned char configuration) { logMessage(LOG_CATEGORY(USB_IO), "setting configuration: %u", configuration); if (configuration == 1) return 1; logUnsupportedFunction(); return 0; } int usbClaimInterface (UsbDevice *device, unsigned char interface) { UsbDeviceExtension *devx = device->extension; logMessage(LOG_CATEGORY(USB_IO), "claiming interface: %u", interface); if (usbSetInterface(devx, interface)) { if (usbOpenConnection(devx)) { if (usbDoClaimInterface(devx->host->env, devx->connection, devx->interface)) { return 1; } } } return 0; } int usbReleaseInterface (UsbDevice *device, unsigned char interface) { UsbDeviceExtension *devx = device->extension; logMessage(LOG_CATEGORY(USB_IO), "releasing interface: %u", interface); if (usbSetInterface(devx, interface)) { if (usbOpenConnection(devx)) { if (usbDoReleaseInterface(devx->host->env, devx->connection, devx->interface)) { return 1; } } } return 0; } int usbSetAlternative ( UsbDevice *device, unsigned char interface, unsigned char alternative ) { logMessage(LOG_CATEGORY(USB_IO), "setting alternative: %u[%u]", interface, alternative); if (alternative == 0) return 1; logUnsupportedFunction(); return 0; } int usbClearHalt (UsbDevice *device, unsigned char endpointAddress) { logMessage(LOG_CATEGORY(USB_IO), "clear halt: %02X", endpointAddress); logUnsupportedFunction(); return 0; } ssize_t usbControlTransfer ( UsbDevice *device, uint8_t direction, uint8_t recipient, uint8_t type, uint8_t request, uint16_t value, uint16_t index, void *buffer, uint16_t length, int timeout ) { ssize_t result = -1; UsbDeviceExtension *devx = device->extension; if (usbOpenConnection(devx)) { const UsbHostDevice *host = devx->host; jbyteArray bytes = (*host->env)->NewByteArray(host->env, length); if (bytes) { if (direction == UsbControlDirection_Output) { (*host->env)->SetByteArrayRegion(host->env, bytes, 0, length, buffer); if (length) logBytes(LOG_CATEGORY(USB_IO), "control output", buffer, length); } result = usbDoControlTransfer(host->env, devx->connection, direction | recipient | type, request, value, index, bytes, length, timeout); if (direction == UsbControlDirection_Input) { if (result > 0) { (*host->env)->GetByteArrayRegion(host->env, bytes, 0, result, buffer); logBytes(LOG_CATEGORY(USB_IO), "control input", buffer, result); } } (*host->env)->DeleteLocalRef(host->env, bytes); } else { logMallocError(); } } if (result == -1) logSystemError("USB control transfer"); return result; } void * usbSubmitRequest ( UsbDevice *device, unsigned char endpointAddress, void *buffer, size_t length, void *context ) { logUnsupportedFunction(); return NULL; } int usbCancelRequest (UsbDevice *device, void *request) { logUnsupportedFunction(); return 0; } void * usbReapResponse ( UsbDevice *device, unsigned char endpointAddress, UsbResponse *response, int wait ) { logUnsupportedFunction(); return NULL; } int usbMonitorInputEndpoint ( UsbDevice *device, unsigned char endpointNumber, AsyncMonitorCallback *callback, void *data ) { return 0; } ssize_t usbReadEndpoint ( UsbDevice *device, unsigned char endpointNumber, void *buffer, size_t length, int timeout ) { ssize_t result = -1; UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber); logMessage(LOG_CATEGORY(USB_IO), "reading endpoint: %u", endpointNumber); if (endpoint) { UsbDeviceExtension *devx = device->extension; if (usbOpenConnection(devx)) { const UsbHostDevice *host = devx->host; JNIEnv *env = host->env; jbyteArray bytes = (*env)->NewByteArray(env, length); if (bytes) { UsbEndpointExtension *eptx = endpoint->extension; result = usbDoBulkTransfer(env, devx->connection, eptx->object, bytes, length, timeout); if (result > 0) (*env)->GetByteArrayRegion(env, bytes, 0, result, buffer); (*env)->DeleteLocalRef(env, bytes); } else { logMallocError(); clearJavaException(env, 0); } } } if (result >= 0) { if (!usbApplyInputFilters(endpoint, buffer, length, &result)) { errno = EIO; result = -1; } } if (result == -1) { if (errno == ETIMEDOUT) errno = EAGAIN; if (errno != EAGAIN) logSystemError("USB bulk read"); } return result; } ssize_t usbWriteEndpoint ( UsbDevice *device, unsigned char endpointNumber, const void *buffer, size_t length, int timeout ) { ssize_t result = -1; UsbEndpoint *endpoint = usbGetOutputEndpoint(device, endpointNumber); if (endpoint) { UsbDeviceExtension *devx = device->extension; usbLogEndpointData(endpoint, "output", buffer, length); if (usbOpenConnection(devx)) { const UsbHostDevice *host = devx->host; JNIEnv *env = host->env; jbyteArray bytes = (*env)->NewByteArray(env, length); if (bytes) { UsbEndpointExtension *eptx = endpoint->extension; (*env)->SetByteArrayRegion(env, bytes, 0, length, buffer); result = usbDoBulkTransfer(env, devx->connection, eptx->object, bytes, length, timeout); (*env)->DeleteLocalRef(env, bytes); } else { logMallocError(); clearJavaException(env, 0); } } } if (result == -1) logSystemError("USB bulk write"); return result; } int usbReadDeviceDescriptor (UsbDevice *device) { device->descriptor = device->extension->host->descriptor; return 1; } int usbAllocateEndpointExtension (UsbEndpoint *endpoint) { UsbDevice *device = endpoint->device; UsbDeviceExtension *devx = device->extension; const UsbHostDevice *host = devx->host; JNIEnv *env = host->env; if (devx->interface) { jobject localReference = usbGetInterfaceEndpoint(env, devx->interface, endpoint->descriptor->bEndpointAddress); if (localReference) { jobject globalReference = (*env)->NewGlobalRef(env, localReference); (*env)->DeleteLocalRef(env, localReference); localReference = NULL; if (globalReference) { UsbEndpointExtension *eptx = malloc(sizeof(*eptx)); if (eptx) { memset(eptx, 0, sizeof(*eptx)); eptx->endpoint = endpoint; eptx->object = globalReference; endpoint->extension = eptx; return 1; } else { logMallocError(); } (*env)->DeleteGlobalRef(env, globalReference); globalReference = NULL; } else { logMallocError(); clearJavaException(env, 0); } } } else { errno = ENOSYS; } return 0; } void usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) { UsbEndpoint *endpoint = eptx->endpoint; UsbDevice *device = endpoint->device; UsbDeviceExtension *devx = device->extension; const UsbHostDevice *host = devx->host; JNIEnv *env = host->env; if (eptx->object) { (*env)->DeleteGlobalRef(env, eptx->object); eptx->object = NULL; } free(eptx); } void usbDeallocateDeviceExtension (UsbDeviceExtension *devx) { usbUnsetInterface(devx); if (devx->connection) { const UsbHostDevice *host = devx->host; usbCloseDeviceConnection(host->env, devx->connection); (*host->env)->DeleteGlobalRef(host->env, devx->connection); devx->connection = NULL; } free(devx); } static void usbDeallocateHostDevice (void *item, void *data) { UsbHostDevice *host = item; (*host->env)->DeleteGlobalRef(host->env, host->device); free(host); } static int usbAddHostDevice (JNIEnv *env, jobject device) { UsbHostDevice *host; if ((host = malloc(sizeof(*host)))) { memset(host, 0, sizeof(*host)); host->env = env; host->descriptor.bLength = UsbDescriptorSize_Device; host->descriptor.bDescriptorType = UsbDescriptorType_Device; host->descriptor.bNumConfigurations = 1; if ((host->device = (*host->env)->NewGlobalRef(host->env, device))) { if (usbGetDeviceVendor(host->env, host->device, &host->descriptor)) { if (usbGetDeviceProduct(host->env, host->device, &host->descriptor)) { if (usbGetDeviceClass(host->env, host->device, &host->descriptor)) { if (usbGetDeviceSubclass(host->env, host->device, &host->descriptor)) { if (usbGetDeviceProtocol(host->env, host->device, &host->descriptor)) { if (enqueueItem(usbHostDevices, host)) { return 1; } } } } } } (*host->env)->DeleteGlobalRef(host->env, host->device); } free(host); } else { logMallocError(); } return 0; } typedef struct { UsbDeviceChooser *chooser; void *data; UsbDevice *device; } UsbTestHostDeviceData; static int usbTestHostDevice (void *item, void *data) { const UsbHostDevice *host = item; UsbTestHostDeviceData *test = data; UsbDeviceExtension *devx; if ((devx = malloc(sizeof(*devx)))) { memset(devx, 0, sizeof(*devx)); devx->host = host; devx->connection = NULL; devx->interface = NULL; if ((test->device = usbTestDevice(devx, test->chooser, test->data))) return 1; usbDeallocateDeviceExtension(devx); } else { logMallocError(); } return 0; } UsbDevice * usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) { if (!usbHostDevices) { int ok = 0; if ((usbHostDevices = newQueue(usbDeallocateHostDevice, NULL))) { JNIEnv *env = getJavaNativeInterface(); if (env) { jobject iterator = usbGetDeviceIterator(env); if (iterator) { jobject device; ok = 1; while ((device = usbGetNextDevice(env, iterator))) { int added = usbAddHostDevice(env, device); (*env)->DeleteLocalRef(env, device); if (!added) { ok = 0; break; } } (*env)->DeleteLocalRef(env, iterator); } } if (!ok) { deallocateQueue(usbHostDevices); usbHostDevices = NULL; } } } if (usbHostDevices) { UsbTestHostDeviceData test = { .chooser = chooser, .data = data, .device = NULL }; if (processQueue(usbHostDevices, usbTestHostDevice, &test)) return test.device; } return NULL; } void usbForgetDevices (void) { if (usbHostDevices) { deallocateQueue(usbHostDevices); usbHostDevices = NULL; } }