From a34a81d120fa2c66522bfcd450003e58dae62d3b Mon Sep 17 00:00:00 2001 From: Christof Arnosti Date: Wed, 11 Mar 2020 11:33:52 +0100 Subject: android usb serial: Prepare device / driver select This commit contains the serial_android_usb part of the changes proposed in issue #2657. What's implemented: - A data structure that contains all the data that can be used to describe an usb device (including user-facing string). - A function to get a list of all attached usb devices (optionally with selectable driver class). - Changes in the serial_android_usb_open-function and in the Java part to use the information about the usb device and optionally selected driver when connecting. This commit keeps compatibility with the current UI-Code in the case that only one USB-Device is connected. If two devices are connected, only the first one is tried. There are still some small things to do: - Change the user-facing string to something more descriptive. - Parts which aren't uesd anymore when the UI-Part is implemented are simply marked as obsolete (to keep compatibility for now). But generally it seems to work. [Dirk Hohndel: some white space / coding style adjustments] Signed-off-by: Christof Arnosti Signed-off-by: Dirk Hohndel --- .../subsurfacedivelog/mobile/AndroidSerial.java | 66 +++++++++++----- core/serial_usb_android.cpp | 88 ++++++++++++++++++++-- core/serial_usb_android.h | 16 ++++ subsurface-mobile-main.cpp | 2 +- 4 files changed, 147 insertions(+), 25 deletions(-) create mode 100644 core/serial_usb_android.h diff --git a/android-mobile/src/org/subsurfacedivelog/mobile/AndroidSerial.java b/android-mobile/src/org/subsurfacedivelog/mobile/AndroidSerial.java index 1193b0ceb..fa26f065e 100644 --- a/android-mobile/src/org/subsurfacedivelog/mobile/AndroidSerial.java +++ b/android-mobile/src/org/subsurfacedivelog/mobile/AndroidSerial.java @@ -14,6 +14,8 @@ import android.content.Intent; import org.subsurfacedivelog.mobile.SubsurfaceMobileActivity; import java.lang.System; +import java.lang.Class; +import java.lang.reflect.Constructor; import java.lang.Thread; import java.util.Queue; import java.util.List; @@ -83,38 +85,68 @@ public class AndroidSerial { this.usbSerialPort = usbSerialPort; } - public static AndroidSerial open_android_serial() + public static AndroidSerial open_android_serial(UsbDevice usbDevice, String driverClassName) { try { Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName()); // Find all available drivers from attached devices. Context context = SubsurfaceMobileActivity.getAppContext(); UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); - ProbeTable usbSerialProbetable = UsbSerialProber.getDefaultProbeTable(); - usbSerialProbetable.addProduct(0x0403, 0xf460, FtdiSerialDriver.class); // Oceanic Custom PID - usbSerialProbetable.addProduct(0x0403, 0xf680, FtdiSerialDriver.class); // Suunto Custom PID - usbSerialProbetable.addProduct(0x0403, 0x87d0, FtdiSerialDriver.class); // Cressi (Leonardo) Custom PID + if (usbDevice == null) { + Log.e(TAG, "usbDevice == null"); + return null; + } - usbSerialProbetable.addProduct(0x04B8, 0x0521, ProlificSerialDriver.class); // Mares (Nemo Sport) / Cressi Custom PID - usbSerialProbetable.addProduct(0x04B8, 0x0521, ProlificSerialDriver.class); // Zeagle Custom PID - usbSerialProbetable.addProduct(0xFFFF, 0x0005, CdcAcmSerialDriver.class); // Mares Icon HD Custom PID + UsbSerialDriver driver = null; - UsbSerialProber usbSerialProber = new UsbSerialProber(usbSerialProbetable); + if (driverClassName.length() == 0) { + ProbeTable usbSerialProbetable = UsbSerialProber.getDefaultProbeTable(); - List availableDrivers = usbSerialProber.findAllDrivers(manager); - if (availableDrivers.isEmpty()) { - Log.w(TAG, "no usb-to-serial devices found!"); - return null; + usbSerialProbetable.addProduct(0x0403, 0xf460, FtdiSerialDriver.class); // Oceanic Custom PID + usbSerialProbetable.addProduct(0x0403, 0xf680, FtdiSerialDriver.class); // Suunto Custom PID + usbSerialProbetable.addProduct(0x0403, 0x87d0, FtdiSerialDriver.class); // Cressi (Leonardo) Custom PID + + usbSerialProbetable.addProduct(0x04B8, 0x0521, ProlificSerialDriver.class); // Mares (Nemo Sport) / Cressi Custom PID + usbSerialProbetable.addProduct(0x04B8, 0x0521, ProlificSerialDriver.class); // Zeagle Custom PID + usbSerialProbetable.addProduct(0xFFFF, 0x0005, CdcAcmSerialDriver.class); // Mares Icon HD Custom PID + + UsbSerialProber usbSerialProber = new UsbSerialProber(usbSerialProbetable); + + driver = usbSerialProber.probeDevice(usbDevice); + + if (driver == null) { + Log.w(TAG, "Could not find a driver for the usb device " + usbDevice); + return null; + } + + Log.i(TAG, "Using autodetected driver class " + driver.getClass().getSimpleName()); + + } else { + final Class driverClass = Class.forName("com.hoho.android.usbserial.driver." + driverClassName).asSubclass(UsbSerialDriver.class); + + if (driverClass == null) { + Log.w(TAG, "Could not find driver class " + driverClassName); + return null; + } + + try { + final Constructor ctor = + driverClass.getConstructor(UsbDevice.class); + driver = ctor.newInstance(usbDevice); + } catch (Exception e) { + Log.w(TAG, "Could not load user-specified driver class " + driverClassName, e); + return null; + } + Log.i(TAG, "Using user-specified driver class " + driver.getClass().getSimpleName()); } // Open a connection to the first available driver. - UsbSerialDriver driver = availableDrivers.get(0); - UsbDeviceConnection connection = manager.openDevice(driver.getDevice()); + UsbDeviceConnection connection = manager.openDevice(usbDevice); if (connection == null) { - manager.requestPermission(driver.getDevice(), PendingIntent.getBroadcast(context, 0, new Intent("org.subsurfacedivelog.mobile.USB_PERMISSION"), 0)); - Log.w(TAG, "Could not open device!"); + manager.requestPermission(usbDevice, PendingIntent.getBroadcast(context, 0, new Intent("org.subsurfacedivelog.mobile.USB_PERMISSION"), 0)); + Log.w(TAG, "Could not open device. Requesting permission."); return null; } diff --git a/core/serial_usb_android.cpp b/core/serial_usb_android.cpp index 8f53c56bb..d53453a47 100644 --- a/core/serial_usb_android.cpp +++ b/core/serial_usb_android.cpp @@ -6,11 +6,14 @@ #include #include +#include #include #include +#include "serial_usb_android.h" + #define INFO(context, fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, __FILE__, "INFO: " fmt "\n", ##__VA_ARGS__) #define ERROR(context, fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, __FILE__, "ERROR: " fmt "\n", ##__VA_ARGS__) #define TRACE INFO @@ -84,8 +87,7 @@ static dc_status_t serial_usb_android_purge(void *io, dc_direction_t direction) return static_cast(device->callMethod("purge", "(I)I", direction)); } -static dc_status_t -serial_usb_android_configure(void *io, unsigned int baudrate, unsigned int databits, dc_parity_t parity, +static dc_status_t serial_usb_android_configure(void *io, unsigned int baudrate, unsigned int databits, dc_parity_t parity, dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol) { TRACE (device->context, "%s: baudrate=%i, databits=%i, parity=%i, stopbits=%i, flowcontrol=%i", __FUNCTION__, @@ -148,7 +150,6 @@ static dc_status_t serial_usb_android_read(void *io, void *data, size_t size, si return DC_STATUS_SUCCESS; } - static dc_status_t serial_usb_android_write(void *io, const void *data, size_t size, size_t *actual) { TRACE (device->context, "%s: size: %i", __FUNCTION__, size); @@ -172,7 +173,7 @@ static dc_status_t serial_usb_android_write(void *io, const void *data, size_t s return DC_STATUS_SUCCESS; } -dc_status_t serial_usb_android_open(dc_iostream_t **iostream, dc_context_t *context) +dc_status_t serial_usb_android_open(dc_iostream_t **iostream, dc_context_t *context, QAndroidJniObject usbDevice, std::string driverClassName) { TRACE(device->contxt, "%s", __FUNCTION__); @@ -191,11 +192,84 @@ dc_status_t serial_usb_android_open(dc_iostream_t **iostream, dc_context_t *cont QAndroidJniObject localdevice = QAndroidJniObject::callStaticObjectMethod("org/subsurfacedivelog/mobile/AndroidSerial", "open_android_serial", - "()Lorg/subsurfacedivelog/mobile/AndroidSerial;"); - if (localdevice == nullptr) { + "(Landroid/hardware/usb/UsbDevice;Ljava/lang/String;)Lorg/subsurfacedivelog/mobile/AndroidSerial;", + usbDevice.object(), + QAndroidJniObject::fromString(driverClassName.c_str()).object()); + if (localdevice == nullptr) return DC_STATUS_IO; - } + QAndroidJniObject *device = new QAndroidJniObject(localdevice); TRACE(device->contxt, "%s", "calling dc_custom_open())"); return dc_custom_open(iostream, context, DC_TRANSPORT_SERIAL, &callbacks, device); } + +std::vector serial_usb_android_get_devices(bool driverSelection) +{ + std::vector driverNames; + if (driverSelection) + driverNames = { "", "CdcAcmSerialDriver", "Ch34xSerialDriver", "Cp21xxSerialDriver", "FtdiSerialDriver", "ProlificSerialDriver" }; + else + driverNames = {""}; + + // Get the current main activity of the application. + QAndroidJniObject activity = QtAndroid::androidActivity(); + QAndroidJniObject usb_service = QAndroidJniObject::fromString("usb"); + QAndroidJniEnvironment env; + + // Get UsbManager from activity + QAndroidJniObject usbManager = activity.callObjectMethod("getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", QAndroidJniObject::fromString("usb").object()); + + //UsbDevice[] arrayOfDevices = usbManager.getDeviceList().values().toArray(); + QAndroidJniObject deviceListHashMap = usbManager.callObjectMethod("getDeviceList","()Ljava/util/HashMap;"); + QAndroidJniObject deviceListCollection = deviceListHashMap.callObjectMethod("values", "()Ljava/util/Collection;"); + jint numDevices = deviceListCollection.callMethod("size"); + QAndroidJniObject arrayOfDevices = deviceListCollection.callObjectMethod("toArray", "()[Ljava/lang/Object;"); + + // Special case to keep a generic user-facing name if only one device is present. + if (numDevices == 1 && !driverSelection) { + // UsbDevice usbDevice = arrayOfDevices[0] + jobject value = env->GetObjectArrayElement(arrayOfDevices.object(), 0); + QAndroidJniObject usbDevice(value); + return std::vector { {QAndroidJniObject(usbDevice), "", "USB Connection"} }; + } else { + std::vector retval; + for (int i = 0; i < numDevices ; i++) { + // UsbDevice usbDevice = arrayOfDevices[i] + jobject value = env->GetObjectArrayElement(arrayOfDevices.object(), i); + QAndroidJniObject usbDevice(value); + + // std::string deviceName = usbDevice.getDeviceName() + QAndroidJniObject usbDeviceNameString = usbDevice.callObjectMethod("getDeviceName"); + const char *charArray = env->GetStringUTFChars(usbDeviceNameString.object(), nullptr); + std::string deviceName(charArray); + env->ReleaseStringUTFChars(usbDeviceNameString.object(), charArray); + + // TODO the deviceName should probably be something better... Currently it's the /dev-filename. + + for (std::string driverName : driverNames) { + std::string uiDeviceName; + if (driverName != "") + uiDeviceName = deviceName + " (" + driverName + ")"; + else + uiDeviceName = deviceName + " (autoselect driver)"; + retval.push_back({QAndroidJniObject(usbDevice), driverName, uiDeviceName}); + } + } + return retval; + } +} + +/* + * For testing and compatibility only, can be removed after the UI changes. Behaves exactly like the "old" + * implementation if only one device is attached. + */ +dc_status_t serial_usb_android_open(dc_iostream_t **iostream, dc_context_t *context) +{ + std::vector devices = serial_usb_android_get_devices(false); + + if(devices.empty()) + return DC_STATUS_NODEVICE; + + return serial_usb_android_open(iostream, context, devices[0].usbDevice, devices[0].className); + +} diff --git a/core/serial_usb_android.h b/core/serial_usb_android.h new file mode 100644 index 000000000..7283950b3 --- /dev/null +++ b/core/serial_usb_android.h @@ -0,0 +1,16 @@ +#ifndef SERIAL_USB_ANDROID_H +#define SERIAL_USB_ANDROID_H + +#include +#include + +/* USB Device Information */ +struct android_usb_serial_device_descriptor { + QAndroidJniObject usbDevice; /* the UsbDevice */ + std::string className; /* the driver class name. If empty, then "autodetect" */ + std::string uiRepresentation; /* The string that can be used for the user interface. */ +}; + +std::vector serial_usb_android_get_devices(bool driverSelection); + +#endif diff --git a/subsurface-mobile-main.cpp b/subsurface-mobile-main.cpp index 9529682df..d1c371a40 100644 --- a/subsurface-mobile-main.cpp +++ b/subsurface-mobile-main.cpp @@ -89,7 +89,7 @@ int main(int argc, char **argv) void set_non_bt_addresses() { #if defined(Q_OS_ANDROID) - connectionListModel.addAddress("usb-serial"); + connectionListModel.addAddress("usb-serial"); /* obsolete, can be removed when the new USB device selection is implemented. */ #elif defined(Q_OS_LINUX) // since this is in the else, it does NOT include Android connectionListModel.addAddress("/dev/ttyS0"); connectionListModel.addAddress("/dev/ttyS1"); -- cgit v1.2.3-70-g09d2