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 <charno@charno.ch>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Christof Arnosti 2020-03-11 11:33:52 +01:00 committed by Dirk Hohndel
parent 822b05bec4
commit a34a81d120
4 changed files with 147 additions and 25 deletions

View file

@ -14,6 +14,8 @@ import android.content.Intent;
import org.subsurfacedivelog.mobile.SubsurfaceMobileActivity; import org.subsurfacedivelog.mobile.SubsurfaceMobileActivity;
import java.lang.System; import java.lang.System;
import java.lang.Class;
import java.lang.reflect.Constructor;
import java.lang.Thread; import java.lang.Thread;
import java.util.Queue; import java.util.Queue;
import java.util.List; import java.util.List;
@ -83,38 +85,68 @@ public class AndroidSerial {
this.usbSerialPort = usbSerialPort; this.usbSerialPort = usbSerialPort;
} }
public static AndroidSerial open_android_serial() public static AndroidSerial open_android_serial(UsbDevice usbDevice, String driverClassName)
{ {
try { try {
Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName()); Log.d(TAG, "in " + Thread.currentThread().getStackTrace()[2].getMethodName());
// Find all available drivers from attached devices. // Find all available drivers from attached devices.
Context context = SubsurfaceMobileActivity.getAppContext(); Context context = SubsurfaceMobileActivity.getAppContext();
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
ProbeTable usbSerialProbetable = UsbSerialProber.getDefaultProbeTable();
usbSerialProbetable.addProduct(0x0403, 0xf460, FtdiSerialDriver.class); // Oceanic Custom PID if (usbDevice == null) {
usbSerialProbetable.addProduct(0x0403, 0xf680, FtdiSerialDriver.class); // Suunto Custom PID Log.e(TAG, "usbDevice == null");
usbSerialProbetable.addProduct(0x0403, 0x87d0, FtdiSerialDriver.class); // Cressi (Leonardo) Custom PID return null;
}
usbSerialProbetable.addProduct(0x04B8, 0x0521, ProlificSerialDriver.class); // Mares (Nemo Sport) / Cressi Custom PID UsbSerialDriver driver = null;
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); if (driverClassName.length() == 0) {
ProbeTable usbSerialProbetable = UsbSerialProber.getDefaultProbeTable();
List<UsbSerialDriver> availableDrivers = usbSerialProber.findAllDrivers(manager); usbSerialProbetable.addProduct(0x0403, 0xf460, FtdiSerialDriver.class); // Oceanic Custom PID
if (availableDrivers.isEmpty()) { usbSerialProbetable.addProduct(0x0403, 0xf680, FtdiSerialDriver.class); // Suunto Custom PID
Log.w(TAG, "no usb-to-serial devices found!"); usbSerialProbetable.addProduct(0x0403, 0x87d0, FtdiSerialDriver.class); // Cressi (Leonardo) Custom PID
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
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<? extends UsbSerialDriver> 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<? extends UsbSerialDriver> 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. // Open a connection to the first available driver.
UsbSerialDriver driver = availableDrivers.get(0); UsbDeviceConnection connection = manager.openDevice(usbDevice);
UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
if (connection == null) { if (connection == null) {
manager.requestPermission(driver.getDevice(), PendingIntent.getBroadcast(context, 0, new Intent("org.subsurfacedivelog.mobile.USB_PERMISSION"), 0)); manager.requestPermission(usbDevice, PendingIntent.getBroadcast(context, 0, new Intent("org.subsurfacedivelog.mobile.USB_PERMISSION"), 0));
Log.w(TAG, "Could not open device!"); Log.w(TAG, "Could not open device. Requesting permission.");
return null; return null;
} }

View file

@ -6,11 +6,14 @@
#include <QAndroidJniObject> #include <QAndroidJniObject>
#include <QAndroidJniEnvironment> #include <QAndroidJniEnvironment>
#include <QtAndroid>
#include <thread> #include <thread>
#include <android/log.h> #include <android/log.h>
#include "serial_usb_android.h"
#define INFO(context, fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, __FILE__, "INFO: " fmt "\n", ##__VA_ARGS__) #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 ERROR(context, fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, __FILE__, "ERROR: " fmt "\n", ##__VA_ARGS__)
#define TRACE INFO #define TRACE INFO
@ -84,8 +87,7 @@ static dc_status_t serial_usb_android_purge(void *io, dc_direction_t direction)
return static_cast<dc_status_t>(device->callMethod<jint>("purge", "(I)I", direction)); return static_cast<dc_status_t>(device->callMethod<jint>("purge", "(I)I", direction));
} }
static dc_status_t static dc_status_t serial_usb_android_configure(void *io, unsigned int baudrate, unsigned int databits, dc_parity_t parity,
serial_usb_android_configure(void *io, unsigned int baudrate, unsigned int databits, dc_parity_t parity,
dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol) dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol)
{ {
TRACE (device->context, "%s: baudrate=%i, databits=%i, parity=%i, stopbits=%i, flowcontrol=%i", __FUNCTION__, 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; return DC_STATUS_SUCCESS;
} }
static dc_status_t serial_usb_android_write(void *io, const void *data, size_t size, size_t *actual) 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); 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; 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__); 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", QAndroidJniObject localdevice = QAndroidJniObject::callStaticObjectMethod("org/subsurfacedivelog/mobile/AndroidSerial",
"open_android_serial", "open_android_serial",
"()Lorg/subsurfacedivelog/mobile/AndroidSerial;"); "(Landroid/hardware/usb/UsbDevice;Ljava/lang/String;)Lorg/subsurfacedivelog/mobile/AndroidSerial;",
if (localdevice == nullptr) { usbDevice.object<jobject>(),
QAndroidJniObject::fromString(driverClassName.c_str()).object());
if (localdevice == nullptr)
return DC_STATUS_IO; return DC_STATUS_IO;
}
QAndroidJniObject *device = new QAndroidJniObject(localdevice); QAndroidJniObject *device = new QAndroidJniObject(localdevice);
TRACE(device->contxt, "%s", "calling dc_custom_open())"); TRACE(device->contxt, "%s", "calling dc_custom_open())");
return dc_custom_open(iostream, context, DC_TRANSPORT_SERIAL, &callbacks, device); return dc_custom_open(iostream, context, DC_TRANSPORT_SERIAL, &callbacks, device);
} }
std::vector<android_usb_serial_device_descriptor> serial_usb_android_get_devices(bool driverSelection)
{
std::vector<std::string> 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<jint>("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<jobjectArray>(), 0);
QAndroidJniObject usbDevice(value);
return std::vector<android_usb_serial_device_descriptor> { {QAndroidJniObject(usbDevice), "", "USB Connection"} };
} else {
std::vector<android_usb_serial_device_descriptor> retval;
for (int i = 0; i < numDevices ; i++) {
// UsbDevice usbDevice = arrayOfDevices[i]
jobject value = env->GetObjectArrayElement(arrayOfDevices.object<jobjectArray>(), i);
QAndroidJniObject usbDevice(value);
// std::string deviceName = usbDevice.getDeviceName()
QAndroidJniObject usbDeviceNameString = usbDevice.callObjectMethod<jstring>("getDeviceName");
const char *charArray = env->GetStringUTFChars(usbDeviceNameString.object<jstring>(), nullptr);
std::string deviceName(charArray);
env->ReleaseStringUTFChars(usbDeviceNameString.object<jstring>(), 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<android_usb_serial_device_descriptor> 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);
}

16
core/serial_usb_android.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef SERIAL_USB_ANDROID_H
#define SERIAL_USB_ANDROID_H
#include <string>
#include <vector>
/* 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<android_usb_serial_device_descriptor> serial_usb_android_get_devices(bool driverSelection);
#endif

View file

@ -89,7 +89,7 @@ int main(int argc, char **argv)
void set_non_bt_addresses() void set_non_bt_addresses()
{ {
#if defined(Q_OS_ANDROID) #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 #elif defined(Q_OS_LINUX) // since this is in the else, it does NOT include Android
connectionListModel.addAddress("/dev/ttyS0"); connectionListModel.addAddress("/dev/ttyS0");
connectionListModel.addAddress("/dev/ttyS1"); connectionListModel.addAddress("/dev/ttyS1");