diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index fa516fef702..0cb949ee05d 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -13,6 +13,7 @@
+
diff --git a/android/build.gradle b/android/build.gradle
index 101adece335..e3b2174a83f 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -5,7 +5,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.6.1'
+ classpath 'com.android.tools.build:gradle:8.7.2'
}
}
@@ -20,7 +20,7 @@ apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'androidx.core:core:1.12.0'
- implementation 'com.github.mik3y:usb-serial-for-android:3.8.0'
+ implementation 'com.github.mik3y:usb-serial-for-android:3.8.1'
}
def getTimestampStr() { return new Date().format('yyyyMMddHHmmss') }
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 0aaefbcaf0f..df97d72b8b9 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/android/libs/qtandroidserialport/CMakeLists.txt b/android/libs/qtandroidserialport/CMakeLists.txt
index 5aa6d61bf7b..f3817ba1fd3 100644
--- a/android/libs/qtandroidserialport/CMakeLists.txt
+++ b/android/libs/qtandroidserialport/CMakeLists.txt
@@ -27,3 +27,5 @@ target_link_libraries(qtandroidserialport
)
target_include_directories(qtandroidserialport PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+
+# target_compile_definitions(qtandroidserialport PUBLIC QIODEVICE_DEBUG)
diff --git a/android/libs/qtandroidserialport/qserialport_android.cpp b/android/libs/qtandroidserialport/qserialport_android.cpp
index 7b5e8b84250..7ea4847daa3 100644
--- a/android/libs/qtandroidserialport/qserialport_android.cpp
+++ b/android/libs/qtandroidserialport/qserialport_android.cpp
@@ -1,12 +1,13 @@
#include "qserialport_p.h"
-#include
+#include "QGCLoggingCategory.h"
#include
#include
-#include
#include
+// TODO: Switch from device ID to serial number to support multiple USB connections
+
QGC_LOGGING_CATEGORY(AndroidSerialPortLog, "qgc.android.libs.qtandroidserialport.qserialport_android")
QT_BEGIN_NAMESPACE
@@ -16,7 +17,7 @@ bool QSerialPortPrivate::open(QIODevice::OpenMode mode)
qCDebug(AndroidSerialPortLog) << "Opening" << systemLocation.toLatin1().constData();
m_deviceId = AndroidSerial::open(systemLocation, this);
- if (m_deviceId == BAD_PORT) {
+ if (m_deviceId == INVALID_DEVICE_ID) {
qCWarning(AndroidSerialPortLog) << "Error opening" << systemLocation.toLatin1().constData();
setError(QSerialPortErrorInfo(QSerialPort::DeviceNotFoundError));
return false;
@@ -24,6 +25,7 @@ bool QSerialPortPrivate::open(QIODevice::OpenMode mode)
descriptor = AndroidSerial::getDeviceHandle(m_deviceId);
if (descriptor == -1) {
+ qCWarning(AndroidSerialPortLog) << "Failed to get device handle for" << systemLocation.toLatin1().constData();
setError(QSerialPortErrorInfo(QSerialPort::OpenError));
close();
return false;
@@ -34,34 +36,41 @@ bool QSerialPortPrivate::open(QIODevice::OpenMode mode)
!setStopBits(stopBits) ||
!setFlowControl(flowControl) ||
!setBaudRate()) {
+ qCWarning(AndroidSerialPortLog) << "Failed to set serial port parameters for" << systemLocation.toLatin1().constData();
close();
return false;
}
if (mode & QIODevice::ReadOnly) {
- // readBufferMaxSize = AndroidSerial::getReadBufferSize();
- startAsyncRead();
+ if (!startAsyncRead()) {
+ qCWarning(AndroidSerialPortLog) << "Failed to start async read for" << systemLocation.toLatin1().constData();
+ close();
+ return false;
+ }
+ } else if (mode & QIODevice::WriteOnly) {
+ if (!_stopAsyncRead()) {
+ qCWarning(AndroidSerialPortLog) << "Failed to stop async read for" << systemLocation.toLatin1().constData();
+ }
}
- /*if (!clear(QSerialPort::AllDirections)) {
- close();
- return false;
- }*/
+ clear(QSerialPort::AllDirections);
return true;
}
void QSerialPortPrivate::close()
{
- qCDebug(AndroidSerialPortLog) << "Closing" << systemLocation.toLatin1().data();
+ qCDebug(AndroidSerialPortLog) << "Closing" << systemLocation.toLatin1().constData();
- descriptor = -1;
- m_pendingBytesWritten = 0;
- m_deviceId = BAD_PORT;
-
- if (!AndroidSerial::close(m_deviceId)) {
- setError(QSerialPortErrorInfo(QSerialPort::ResourceError, QStringLiteral("Closing device failed")));
+ if (m_deviceId != INVALID_DEVICE_ID) {
+ if (!AndroidSerial::close(m_deviceId)) {
+ qCWarning(AndroidSerialPortLog) << "Failed to close device with ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Closing device failed")));
+ }
+ m_deviceId = INVALID_DEVICE_ID;
}
+
+ descriptor = -1;
}
bool QSerialPortPrivate::_stopAsyncRead()
@@ -69,7 +78,14 @@ bool QSerialPortPrivate::_stopAsyncRead()
if (!AndroidSerial::readThreadRunning(m_deviceId)) {
return true;
}
- return AndroidSerial::stopReadThread(m_deviceId);
+
+ const bool result = AndroidSerial::stopReadThread(m_deviceId);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to stop async read thread for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to stop async read")));
+ }
+
+ return result;
}
bool QSerialPortPrivate::startAsyncRead()
@@ -77,21 +93,27 @@ bool QSerialPortPrivate::startAsyncRead()
if (AndroidSerial::readThreadRunning(m_deviceId)) {
return true;
}
- return AndroidSerial::startReadThread(m_deviceId);
+
+ const bool result = AndroidSerial::startReadThread(m_deviceId);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to start async read thread for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to start async read")));
+ }
+
+ return result;
}
-void QSerialPortPrivate::newDataArrived(char *bytes, int length)
+void QSerialPortPrivate::newDataArrived(const char *bytes, int length)
{
Q_Q(QSerialPort);
- if (!q->isOpen()) {
- return;
- }
+ qCDebug(AndroidSerialPortLog) << "newDataArrived" << length;
int bytesToRead = length;
if (readBufferMaxSize && (bytesToRead > (readBufferMaxSize - buffer.size()))) {
bytesToRead = static_cast(readBufferMaxSize - buffer.size());
if (bytesToRead <= 0) {
+ qCWarning(AndroidSerialPortLog) << "Read buffer exceeded maximum size. Stopping async read.";
(void) _stopAsyncRead();
return;
}
@@ -100,11 +122,13 @@ void QSerialPortPrivate::newDataArrived(char *bytes, int length)
char* const ptr = buffer.reserve(bytesToRead);
(void) memcpy(ptr, bytes, static_cast(bytesToRead));
+ // TODO: Limit signals as one read can handle multiple signals
emit q->readyRead();
}
void QSerialPortPrivate::exceptionArrived(const QString &ex)
{
+ qCWarning(AndroidSerialPortLog) << "Exception arrived on device ID" << m_deviceId << ":" << ex;
setError(QSerialPortErrorInfo(QSerialPort::UnknownError, ex));
}
@@ -124,54 +148,85 @@ bool QSerialPortPrivate::waitForReadyRead(int msecs)
}
}
+ qCWarning(AndroidSerialPortLog) << "Timeout while waiting for ready read on device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::TimeoutError, QSerialPort::tr("Timeout while waiting for ready read")));
+
return false;
}
bool QSerialPortPrivate::waitForBytesWritten(int msecs)
{
- // if (writeBuffer.isEmpty() && (m_pendingBytesWritten <= 0)) {
- // return false;
- // }
+ const bool result = _writeDataOneShot(msecs);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Timeout while waiting for bytes written on device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::TimeoutError, QSerialPort::tr("Timeout while waiting for bytes written")));
+ }
- return _writeDataOneShot(msecs);
+ return result;
}
bool QSerialPortPrivate::_writeDataOneShot(int msecs)
{
Q_Q(QSerialPort);
- m_pendingBytesWritten = -1;
+ qint64 pendingBytesWritten = -1;
while (!writeBuffer.isEmpty()) {
- m_pendingBytesWritten = _writeToPort(writeBuffer.readPointer(), writeBuffer.nextDataBlockSize(), msecs, false);
+ const char *dataPtr = writeBuffer.readPointer();
+ const qint64 dataSize = writeBuffer.nextDataBlockSize();
+
+ pendingBytesWritten = _writeToPort(dataPtr, dataSize, msecs);
- if (m_pendingBytesWritten <= 0) {
- setError(QSerialPortErrorInfo(QSerialPort::WriteError));
+ if (pendingBytesWritten <= 0) {
+ qCWarning(AndroidSerialPortLog) << "Failed to write data one shot on device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::WriteError, QSerialPort::tr("Failed to write data one shot")));
return false;
}
- writeBuffer.free(m_pendingBytesWritten);
+ writeBuffer.free(pendingBytesWritten);
- emit q->bytesWritten(m_pendingBytesWritten);
+ emit q->bytesWritten(pendingBytesWritten);
}
- return (m_pendingBytesWritten >= 0);
+ return (pendingBytesWritten >= 0);
}
qint64 QSerialPortPrivate::_writeToPort(const char *data, qint64 maxSize, int timeout, bool async)
{
- return AndroidSerial::write(m_deviceId, data, maxSize, timeout, async);
+ const qint64 result = AndroidSerial::write(m_deviceId, data, maxSize, timeout, async);
+ if (result < 0) {
+ qCWarning(AndroidSerialPortLog) << "Failed to write to port ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::WriteError, QSerialPort::tr("Failed to write to port")));
+ }
+
+ return result;
}
qint64 QSerialPortPrivate::writeData(const char *data, qint64 maxSize)
{
- (void) writeBuffer.append(data, maxSize);
- return _writeDataOneShot(0);
+ if (!data || maxSize <= 0) {
+ qCWarning(AndroidSerialPortLog) << "Invalid data or size in writeData for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::WriteError, QSerialPort::tr("Invalid data or size")));
+ return -1;
+ }
+
+ const qint64 result = _writeToPort(data, maxSize);
+ if (result < 0) {
+ setError(QSerialPortErrorInfo(QSerialPort::WriteError, QSerialPort::tr("Failed to write data")));
+ }
+
+ return result;
}
bool QSerialPortPrivate::flush()
{
- return _writeDataOneShot(0);
+ const bool result = _writeDataOneShot(0);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Flush operation failed for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to flush")));
+ }
+
+ return result;
}
bool QSerialPortPrivate::clear(QSerialPort::Directions directions)
@@ -191,7 +246,13 @@ bool QSerialPortPrivate::clear(QSerialPort::Directions directions)
}
}
- return AndroidSerial::purgeBuffers(m_deviceId, input, output);
+ const bool result = AndroidSerial::purgeBuffers(m_deviceId, input, output);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to purge buffers for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to purge buffers")));
+ }
+
+ return result;
}
QSerialPort::PinoutSignals QSerialPortPrivate::pinoutSignals()
@@ -201,22 +262,32 @@ QSerialPort::PinoutSignals QSerialPortPrivate::pinoutSignals()
bool QSerialPortPrivate::setDataTerminalReady(bool set)
{
- return AndroidSerial::setDataTerminalReady(m_deviceId, set);
+ const bool result = AndroidSerial::setDataTerminalReady(m_deviceId, set);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to set DTR for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to set DTR")));
+ }
+
+ return result;
}
bool QSerialPortPrivate::setRequestToSend(bool set)
{
- return AndroidSerial::setRequestToSend(m_deviceId, set);
+ const bool result = AndroidSerial::setRequestToSend(m_deviceId, set);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to set RTS for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to set RTS")));
+ }
+
+ return result;
}
-bool QSerialPortPrivate::_setParameters(int baudRate, int dataBits, int stopBits, int parity)
+bool QSerialPortPrivate::_setParameters(qint32 baudRate, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, QSerialPort::Parity parity)
{
- const bool result = AndroidSerial::setParameters(m_deviceId, baudRate, dataBits, stopBits, parity);
- if (result) {
- inputBaudRate = outputBaudRate = baudRate;
- m_dataBits = dataBits;
- m_stopBits = stopBits;
- m_parity = parity;
+ const bool result = AndroidSerial::setParameters(m_deviceId, baudRate, _dataBitsToAndroidDataBits(dataBits), _stopBitsToAndroidStopBits(stopBits), _parityToAndroidParity(parity));
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to set Parameters for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to set parameters")));
}
return result;
@@ -230,116 +301,154 @@ bool QSerialPortPrivate::setBaudRate()
bool QSerialPortPrivate::setBaudRate(qint32 baudRate, QSerialPort::Directions directions)
{
if (baudRate <= 0) {
+ qCWarning(AndroidSerialPortLog) << "Invalid baud rate value:" << baudRate;
setError(QSerialPortErrorInfo(QSerialPort::UnsupportedOperationError, QSerialPort::tr("Invalid baud rate value")));
return false;
}
if (directions != QSerialPort::AllDirections) {
+ qCWarning(AndroidSerialPortLog) << "Custom baud rate direction is unsupported:" << directions;
setError(QSerialPortErrorInfo(QSerialPort::UnsupportedOperationError, QSerialPort::tr("Custom baud rate direction is unsupported")));
return false;
}
- const qint32 standardBaudRate = QSerialPortPrivate::_settingFromBaudRate(baudRate);
+ const qint32 standardBaudRate = _settingFromBaudRate(baudRate);
if (standardBaudRate <= 0) {
+ qCWarning(AndroidSerialPortLog) << "Invalid Baud Rate:" << baudRate;
setError(QSerialPortErrorInfo(QSerialPort::UnsupportedOperationError, QSerialPort::tr("Invalid Baud Rate")));
return false;
}
- return _setParameters(baudRate, m_dataBits, m_stopBits, m_parity);
+ const bool result = _setParameters(baudRate, dataBits, stopBits, parity);
+ if (result) {
+ inputBaudRate = outputBaudRate = baudRate;
+ } else {
+ qCWarning(AndroidSerialPortLog) << "Failed to set baud rate for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to set baud rate")));
+ }
+
+ return result;
}
-bool QSerialPortPrivate::setDataBits(QSerialPort::DataBits dataBits)
-{
- int numBits;
+int QSerialPortPrivate::_dataBitsToAndroidDataBits(QSerialPort::DataBits dataBits)
+{
switch (dataBits) {
case QSerialPort::Data5:
- numBits = AndroidSerial::Data5;
- break;
+ return AndroidSerial::Data5;
case QSerialPort::Data6:
- numBits = AndroidSerial::Data6;
- break;
+ return AndroidSerial::Data6;
case QSerialPort::Data7:
- numBits = AndroidSerial::Data7;
- break;
+ return AndroidSerial::Data7;
case QSerialPort::Data8:
+ return AndroidSerial::Data8;
default:
- numBits = AndroidSerial::Data8;
- break;
+ qCWarning(AndroidSerialPortLog) << "Invalid Data Bits" << dataBits;
+ return AndroidSerial::Data8; // Default to Data8
}
-
- return _setParameters(inputBaudRate, numBits, m_stopBits, m_parity);
}
-bool QSerialPortPrivate::setParity(QSerialPort::Parity parity)
+bool QSerialPortPrivate::setDataBits(QSerialPort::DataBits dataBits)
{
- int par;
+ const bool result = _setParameters(inputBaudRate, dataBits, stopBits, parity);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to set data bits for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to set data bits")));
+ }
+ return result;
+}
+
+int QSerialPortPrivate::_parityToAndroidParity(QSerialPort::Parity parity)
+{
switch (parity) {
case QSerialPort::SpaceParity:
- par = AndroidSerial::SpaceParity;
- break;
+ return AndroidSerial::SpaceParity;
case QSerialPort::MarkParity:
- par = AndroidSerial::MarkParity;
- break;
+ return AndroidSerial::MarkParity;
case QSerialPort::EvenParity:
- par = AndroidSerial::EvenParity;
- break;
+ return AndroidSerial::EvenParity;
case QSerialPort::OddParity:
- par = AndroidSerial::OddParity;
- break;
+ return AndroidSerial::OddParity;
case QSerialPort::NoParity:
+ return AndroidSerial::NoParity;
default:
- par = AndroidSerial::NoParity;
- break;
+ qCWarning(AndroidSerialPortLog) << "Invalid parity type:" << parity;
+ return AndroidSerial::NoParity; // Default to NoParity
}
-
- return _setParameters(inputBaudRate, m_dataBits, m_stopBits, par);
}
-bool QSerialPortPrivate::setStopBits(QSerialPort::StopBits stopBits)
+bool QSerialPortPrivate::setParity(QSerialPort::Parity parity)
{
- int stop;
+ const bool result = _setParameters(inputBaudRate, dataBits, stopBits, parity);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to set parity for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to set parity")));
+ }
+
+ return result;
+}
+int QSerialPortPrivate::_stopBitsToAndroidStopBits(QSerialPort::StopBits stopBits)
+{
switch (stopBits) {
case QSerialPort::TwoStop:
- stop = AndroidSerial::TwoStop;
- break;
+ return AndroidSerial::TwoStop;
case QSerialPort::OneAndHalfStop:
- stop = AndroidSerial::OneAndHalfStop;
- break;
+ return AndroidSerial::OneAndHalfStop;
case QSerialPort::OneStop:
+ return AndroidSerial::OneStop;
default:
- stop = AndroidSerial::OneStop;
- break;
+ qCWarning(AndroidSerialPortLog) << "Invalid Stop Bits type:" << stopBits;
+ return AndroidSerial::OneStop; // Default to OneStop
}
-
- return _setParameters(inputBaudRate, m_dataBits, stop, m_parity);
}
-bool QSerialPortPrivate::setFlowControl(QSerialPort::FlowControl flowControl)
+bool QSerialPortPrivate::setStopBits(QSerialPort::StopBits stopBits)
{
- int control;
+ const bool result = _setParameters(inputBaudRate, dataBits, stopBits, parity);
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to set StopBits for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to set StopBits")));
+ }
+
+ return result;
+}
+int QSerialPortPrivate::_flowControlToAndroidFlowControl(QSerialPort::FlowControl flowControl)
+{
switch (flowControl) {
case QSerialPort::HardwareControl:
- control = AndroidSerial::RtsCtsFlowControl;
- break;
+ return AndroidSerial::RtsCtsFlowControl;
case QSerialPort::SoftwareControl:
- control = AndroidSerial::XonXoffFlowControl;
- break;
+ return AndroidSerial::XonXoffFlowControl;
case QSerialPort::NoFlowControl:
+ return AndroidSerial::NoFlowControl;
default:
- control = AndroidSerial::NoFlowControl;
- break;
+ qCWarning(AndroidSerialPortLog) << "Invalid Flow Control type:" << flowControl;
+ return AndroidSerial::NoFlowControl; // Default to NoFlowControl
}
+}
- return AndroidSerial::setFlowControl(m_deviceId, control);
+bool QSerialPortPrivate::setFlowControl(QSerialPort::FlowControl flowControl)
+{
+ const bool result = AndroidSerial::setFlowControl(m_deviceId, _flowControlToAndroidFlowControl(flowControl));
+ if (!result) {
+ qCWarning(AndroidSerialPortLog) << "Failed to set Flow Control for device ID" << m_deviceId;
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to set Flow Control")));
+ }
+
+ return result;
}
bool QSerialPortPrivate::setBreakEnabled(bool set)
{
- return AndroidSerial::setBreak(m_deviceId, set);
+ const bool result = AndroidSerial::setBreak(m_deviceId, set);
+ if (!result) {
+ setError(QSerialPortErrorInfo(QSerialPort::UnknownError, QSerialPort::tr("Failed to set Break Enabled")));
+ }
+
+ return result;
}
typedef QMap BaudRateMap;
diff --git a/android/libs/qtandroidserialport/qserialport_p.h b/android/libs/qtandroidserialport/qserialport_p.h
index 08923cb5833..22ac8d65fda 100644
--- a/android/libs/qtandroidserialport/qserialport_p.h
+++ b/android/libs/qtandroidserialport/qserialport_p.h
@@ -20,15 +20,22 @@
#include
#include
#include
+#include
+#include
#include "qserialport.h"
-#include
+#include "AndroidSerial.h"
-#define BAD_PORT 0
-#define MAX_READ_SIZE (16 * 1024);
+constexpr int INVALID_DEVICE_ID = 0;
+constexpr int MIN_READ_TIMEOUT = 500;
+constexpr qint64 MAX_READ_SIZE = 16 * 1024;
+constexpr qint64 DEFAULT_READ_BUFFER_SIZE = MAX_READ_SIZE;
+constexpr qint64 DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
+constexpr int DEFAULT_WRITE_TIMEOUT = 0;
+constexpr int DEFAULT_READ_TIMEOUT = 0;
#ifndef QSERIALPORT_BUFFERSIZE
-#define QSERIALPORT_BUFFERSIZE 32768
+#define QSERIALPORT_BUFFERSIZE DEFAULT_WRITE_BUFFER_SIZE
#endif
Q_DECLARE_LOGGING_CATEGORY(AndroidSerialPortLog)
@@ -110,7 +117,7 @@ class QSerialPortPrivate : public QIODevicePrivate
qint64 writeData(const char *data, qint64 maxSize);
- void newDataArrived(char *bytes, int length);
+ void newDataArrived(const char *bytes, int length);
void exceptionArrived(const QString &ex);
static QList standardBaudRates();
@@ -118,23 +125,22 @@ class QSerialPortPrivate : public QIODevicePrivate
QString systemLocation;
qint32 inputBaudRate = QSerialPort::Baud9600;
qint32 outputBaudRate = QSerialPort::Baud9600;
- qint64 readBufferMaxSize = 0;
+ qint64 readBufferMaxSize = 0; // DEFAULT_READ_BUFFER_SIZE
int descriptor = -1;
private:
- qint64 _writeToPort(const char *data, qint64 maxSize, int timeout = 0, bool async = false);
+ qint64 _writeToPort(const char *data, qint64 maxSize, int timeout = DEFAULT_WRITE_TIMEOUT, bool async = false);
bool _stopAsyncRead();
- bool _setParameters(int baudRate, int dataBits, int stopBits, int parity);
+ bool _setParameters(qint32 baudRate, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, QSerialPort::Parity parity);
bool _writeDataOneShot(int msecs);
static qint32 _settingFromBaudRate(qint32 baudRate);
+ static int _stopBitsToAndroidStopBits(QSerialPort::StopBits stopBits);
+ static int _dataBitsToAndroidDataBits(QSerialPort::DataBits dataBits);
+ static int _parityToAndroidParity(QSerialPort::Parity parity);
+ static int _flowControlToAndroidFlowControl(QSerialPort::FlowControl flowControl);
- qint64 m_pendingBytesWritten = 0;
-
- int m_deviceId = BAD_PORT;
- int m_dataBits = AndroidSerial::Data8;
- int m_stopBits = AndroidSerial::OneStop;
- int m_parity = AndroidSerial::NoParity;
+ int m_deviceId = INVALID_DEVICE_ID;
};
QT_END_NAMESPACE
diff --git a/android/src/AndroidInit.cpp b/android/src/AndroidInit.cpp
index b1833df3bd7..d59ea92a114 100644
--- a/android/src/AndroidInit.cpp
+++ b/android/src/AndroidInit.cpp
@@ -9,7 +9,8 @@
#include
#include
-QGC_LOGGING_CATEGORY(AndroidInitLog, "qgc.android.init");
+QGC_LOGGING_CATEGORY(AndroidInitLog, "qgc.android.androidinit");
+
static jobject _context = nullptr;
static jobject _class_loader = nullptr;
@@ -25,27 +26,29 @@ extern "C"
}
#endif
-static void jniInit(JNIEnv *env, jobject context)
+static jboolean jniInit(JNIEnv *env, jobject context)
{
qCDebug(AndroidInitLog) << Q_FUNC_INFO;
const jclass context_cls = env->GetObjectClass(context);
if (!context_cls) {
- return;
+ return JNI_FALSE;
}
const jmethodID get_class_loader_id = env->GetMethodID(context_cls, "getClassLoader", "()Ljava/lang/ClassLoader;");
if (QJniEnvironment::checkAndClearExceptions(env)) {
- return;
+ return JNI_FALSE;
}
const jobject class_loader = env->CallObjectMethod(context, get_class_loader_id);
if (QJniEnvironment::checkAndClearExceptions(env)) {
- return;
+ return JNI_FALSE;
}
_context = env->NewGlobalRef(context);
_class_loader = env->NewGlobalRef(class_loader);
+
+ return JNI_TRUE;
}
static jint jniSetNativeMethods()
@@ -53,7 +56,7 @@ static jint jniSetNativeMethods()
qCDebug(AndroidInitLog) << Q_FUNC_INFO;
const JNINativeMethod javaMethods[] {
- {"nativeInit", "()V", reinterpret_cast(jniInit)}
+ {"nativeInit", "()Z", reinterpret_cast(jniInit)}
};
QJniEnvironment jniEnv;
@@ -62,16 +65,19 @@ static jint jniSetNativeMethods()
jclass objectClass = jniEnv->FindClass(AndroidInterface::kJniQGCActivityClassName);
if (!objectClass) {
qCWarning(AndroidInitLog) << "Couldn't find class:" << AndroidInterface::kJniQGCActivityClassName;
+ (void) jniEnv.checkAndClearExceptions();
return JNI_ERR;
}
- const jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0]));
+ const jint val = jniEnv->RegisterNatives(objectClass, javaMethods, std::size(javaMethods));
if (val < 0) {
qCWarning(AndroidInitLog) << "Error registering methods:" << val;
- } else {
- qCDebug(AndroidInitLog) << "Main Native Functions Registered";
+ (void) jniEnv.checkAndClearExceptions();
+ return JNI_ERR;
}
+ qCDebug(AndroidInitLog) << "Main Native Functions Registered";
+
(void) jniEnv.checkAndClearExceptions();
return JNI_OK;
diff --git a/android/src/AndroidInterface.cc b/android/src/AndroidInterface.cc
index 1717b4a3c8a..f8c0874314d 100644
--- a/android/src/AndroidInterface.cc
+++ b/android/src/AndroidInterface.cc
@@ -8,7 +8,7 @@
****************************************************************************/
#include "AndroidInterface.h"
-#include
+#include "QGCLoggingCategory.h"
#include
#include
@@ -44,7 +44,7 @@ void setNativeMethods()
}
QJniEnvironment jniEnv;
- jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0]));
+ jint val = jniEnv->RegisterNatives(objectClass, javaMethods, std::size(javaMethods));
if (val < 0) {
qCWarning(AndroidInterfaceLog) << "Error registering methods:" << val;
diff --git a/android/src/AndroidSerial.cc b/android/src/AndroidSerial.cc
index e59185aea5c..3fa15940dc6 100644
--- a/android/src/AndroidSerial.cc
+++ b/android/src/AndroidSerial.cc
@@ -1,62 +1,105 @@
#include "AndroidSerial.h"
-#include "AndroidInterface.h"
+#include "QGCLoggingCategory.h"
#include
#include
-#include
-#include
-#include
#include
#include
-#include
+QGC_LOGGING_CATEGORY(AndroidSerialLog, "qgc.android.androidserial");
-Q_LOGGING_CATEGORY(AndroidSerialLog, "qgc.android.serial");
+// TODO: Save Method Lookups
namespace AndroidSerial
{
+jclass getSerialManagerClass()
+{
+ static jclass javaClass = nullptr;
+
+ if (!javaClass) {
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment";
+ return nullptr;
+ }
+
+ if (!QJniObject::isClassAvailable(kJniUsbSerialManagerClassName)) {
+ qCWarning(AndroidSerialLog) << "Class Not Available:" << kJniUsbSerialManagerClassName;
+ return nullptr;
+ }
+
+ javaClass = env.findClass(kJniUsbSerialManagerClassName);
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "Class Not Found:" << kJniUsbSerialManagerClassName;
+ return nullptr;
+ }
+
+ (void) env.checkAndClearExceptions();
+ }
+
+ return javaClass;
+}
+
void setNativeMethods()
{
qCDebug(AndroidSerialLog) << "Registering Native Functions";
- JNINativeMethod javaMethods[] {
+ const JNINativeMethod javaMethods[] {
{"nativeDeviceHasDisconnected", "(J)V", reinterpret_cast(jniDeviceHasDisconnected)},
{"nativeDeviceNewData", "(J[B)V", reinterpret_cast(jniDeviceNewData)},
{"nativeDeviceException", "(JLjava/lang/String;)V", reinterpret_cast(jniDeviceException)},
};
- (void) AndroidInterface::cleanJavaException();
-
- jclass objectClass = AndroidInterface::getActivityClass();
- if (!objectClass) {
- qCWarning(AndroidSerialLog) << "Couldn't find class:" << objectClass;
+ QJniEnvironment jniEnv;
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "Couldn't find class for RegisterNatives:" << kJniUsbSerialManagerClassName;
+ (void) jniEnv.checkAndClearExceptions();
return;
}
- QJniEnvironment jniEnv;
- jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0]));
-
- if (val < 0) {
- qCWarning(AndroidSerialLog) << "Error registering methods: " << val;
- } else {
- qCDebug(AndroidSerialLog) << "Native Functions Registered";
+ const jint regResult = jniEnv->RegisterNatives(javaClass, javaMethods, std::size(javaMethods));
+ if (regResult != JNI_OK) {
+ qCWarning(AndroidSerialLog) << "Error registering native methods:" << regResult;
+ (void) jniEnv.checkAndClearExceptions();
+ return;
}
+ qCDebug(AndroidSerialLog) << "Native Functions Registered Successfully";
+
(void) jniEnv.checkAndClearExceptions();
}
void jniDeviceHasDisconnected(JNIEnv *env, jobject obj, jlong classPtr)
{
- Q_UNUSED(env);
- Q_UNUSED(obj);
+ Q_UNUSED(env); Q_UNUSED(obj);
+
+ if (classPtr == 0) {
+ qCWarning(AndroidSerialLog) << "nativeDeviceHasDisconnected called with classPtr=0";
+ return;
+ }
+
+ QSerialPortPrivate* const serialPortPrivate = reinterpret_cast(classPtr);
+ if (!serialPortPrivate) {
+ qCWarning(AndroidSerialLog) << "serialPortPrivate is null in nativeDeviceHasDisconnected";
+ return;
+ }
+
+ QSerialPort* const serialPort = qobject_cast(serialPortPrivate->q_ptr);
+ if (!serialPort) {
+ qCWarning(AndroidSerialLog) << "serialPort is null in nativeDeviceHasDisconnected";
+ return;
+ }
+
+ qCDebug(AndroidSerialLog) << "Device disconnected:" << serialPort->portName();
- if (classPtr != 0) {
- QSerialPortPrivate* const serialPortPrivate = reinterpret_cast(classPtr);
- qCDebug(AndroidSerialLog) << "Device disconnected" << serialPortPrivate->systemLocation.toLatin1().constData();
- QSerialPort* const serialPort = static_cast(serialPortPrivate->q_ptr);
+ if (serialPort->isOpen()) {
serialPort->close();
+ qCDebug(AndroidSerialLog) << "Serial port closed in nativeDeviceHasDisconnected";
+ } else {
+ qCWarning(AndroidSerialLog) << "Serial port was already closed in nativeDeviceHasDisconnected";
}
}
@@ -64,14 +107,41 @@ void jniDeviceNewData(JNIEnv *env, jobject obj, jlong classPtr, jbyteArray data)
{
Q_UNUSED(obj);
- if (classPtr != 0) {
- jbyte* const bytes = env->GetByteArrayElements(data, nullptr);
- const jsize len = env->GetArrayLength(data);
- // QByteArray data = QByteArray::fromRawData(reinterpret_cast(bytes), len);
- QSerialPortPrivate* const serialPort = reinterpret_cast(classPtr);
- serialPort->newDataArrived(reinterpret_cast(bytes), len);
- env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
- (void) QJniEnvironment::checkAndClearExceptions(env);
+ if (classPtr == 0) {
+ qCWarning(AndroidSerialLog) << "nativeDeviceNewData called with classPtr=0";
+ return;
+ }
+
+ if (!data) {
+ qCWarning(AndroidSerialLog) << "nativeDeviceNewData called with null data";
+ return;
+ }
+
+ const jsize len = env->GetArrayLength(data);
+ if (len <= 0) {
+ qCWarning(AndroidSerialLog) << "nativeDeviceNewData received empty data array";
+ return;
+ }
+
+ jbyte* const bytes = env->GetByteArrayElements(data, nullptr);
+ if (!bytes) {
+ qCWarning(AndroidSerialLog) << "Failed to get byte array elements";
+ return;
+ }
+
+ const QByteArray byteArray(reinterpret_cast(bytes), len);
+ env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
+
+ QSerialPortPrivate* const serialPortPrivate = reinterpret_cast(classPtr);
+ if (!serialPortPrivate) {
+ qCWarning(AndroidSerialLog) << "serialPortPrivate is null";
+ return;
+ }
+
+ serialPortPrivate->newDataArrived(byteArray.constData(), byteArray.size());
+
+ if (QJniEnvironment::checkAndClearExceptions(env)) {
+ qCWarning(AndroidSerialLog) << "Exception occurred in nativeDeviceNewData";
}
}
@@ -79,71 +149,115 @@ void jniDeviceException(JNIEnv *env, jobject obj, jlong classPtr, jstring messag
{
Q_UNUSED(obj);
- if (classPtr != 0) {
- const char* const string = env->GetStringUTFChars(message, nullptr);
- const QString str = QString::fromUtf8(string);
- QSerialPortPrivate* const serialPort = reinterpret_cast(classPtr);
- serialPort->exceptionArrived(str);
- env->ReleaseStringUTFChars(message, string);
- (void) QJniEnvironment::checkAndClearExceptions(env);
+ if (classPtr == 0) {
+ qCWarning(AndroidSerialLog) << "nativeDeviceException called with classPtr=0";
+ return;
+ }
+
+ if (!message) {
+ qCWarning(AndroidSerialLog) << "nativeDeviceException called with null message";
+ return;
+ }
+
+ const char *const rawMessage = env->GetStringUTFChars(message, nullptr);
+ if (!rawMessage) {
+ qCWarning(AndroidSerialLog) << "Failed to get UTF chars from jstring";
+ return;
+ }
+
+ const QString exceptionMessage = QString::fromUtf8(rawMessage);
+ env->ReleaseStringUTFChars(message, rawMessage);
+
+ QSerialPortPrivate* const serialPortPrivate = reinterpret_cast(classPtr);
+ if (!serialPortPrivate) {
+ qCWarning(AndroidSerialLog) << "serialPortPrivate is null";
+ return;
+ }
+
+ serialPortPrivate->exceptionArrived(exceptionMessage);
+ qCWarning(AndroidSerialLog) << "Exception from Java:" << exceptionMessage;
+
+ if (QJniEnvironment::checkAndClearExceptions(env)) {
+ qCWarning(AndroidSerialLog) << "Exception occurred in nativeDeviceException";
}
}
QList availableDevices()
{
- jclass javaClass = AndroidInterface::getActivityClass();
+ QList serialPortInfoList;
+
+ const jclass javaClass = getSerialManagerClass();
if (!javaClass) {
- qCWarning(AndroidSerialLog) << "Class Not Found";
- return QList();
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in availableDevices";
+ return serialPortInfoList;
}
QJniEnvironment env;
- const jmethodID methodId = env.findStaticMethod(javaClass, "availableDevicesInfo", "()[Ljava/lang/String;");
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in availableDevices";
+ return serialPortInfoList;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "availableDevicesInfo", "()[Ljava/lang/String;");
if (!methodId) {
- qCWarning(AndroidSerialLog) << "Method Not Found";
- return QList();
+ qCWarning(AndroidSerialLog) << "Method Not Found: availableDevicesInfo";
+ env.checkAndClearExceptions();
+ return serialPortInfoList;
}
- const QJniObject result = QJniObject::callStaticObjectMethod(javaClass, methodId);
- if (!result.isValid()) {
- qCDebug(AndroidSerialLog) << "Method Call Failed";
- return QList();
+ jobjectArray objArray = static_cast(env->CallStaticObjectMethod(javaClass, methodId));
+ if (!objArray) {
+ qCWarning(AndroidSerialLog) << "availableDevicesInfo returned null";
+ (void) env.checkAndClearExceptions();
+ return serialPortInfoList;
}
- QList serialPortInfoList;
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling availableDevicesInfo";
+ env->DeleteLocalRef(objArray);
+ return serialPortInfoList;
+ }
- const jobjectArray objArray = result.object();
const jsize count = env->GetArrayLength(objArray);
+ for (jsize i = 0; i < count; ++i) {
+ jstring jstr = static_cast(env->GetObjectArrayElement(objArray, i));
+ if (!jstr) {
+ qCWarning(AndroidSerialLog) << "Null string at index" << i;
+ continue;
+ }
- for (jsize i = 0; i < count; i++) {
- jobject obj = env->GetObjectArrayElement(objArray, i);
- jstring string = static_cast(obj);
- const char * const rawString = env->GetStringUTFChars(string, 0);
- qCDebug(AndroidSerialLog) << "Adding device" << rawString;
+ const char *const rawString = env->GetStringUTFChars(jstr, nullptr);
+ if (!rawString) {
+ qCWarning(AndroidSerialLog) << "Failed to get UTF chars from jstring at index" << i;
+ env->DeleteLocalRef(jstr);
+ continue;
+ }
- const QStringList strList = QString::fromUtf8(rawString).split(QStringLiteral(":"));
- env->ReleaseStringUTFChars(string, rawString);
- env->DeleteLocalRef(string);
+ const QStringList strList = QString::fromUtf8(rawString).split(':');
+ env->ReleaseStringUTFChars(jstr, rawString);
+ env->DeleteLocalRef(jstr);
if (strList.size() < 6) {
- qCWarning(AndroidSerialLog) << "Invalid device info";
+ qCWarning(AndroidSerialLog) << "Invalid device info at index" << i << ":" << strList;
continue;
}
- QSerialPortInfoPrivate priv;
-
- priv.portName = QSerialPortInfoPrivate::portNameFromSystemLocation(strList.at(0));
- priv.device = strList.at(0);
- priv.description = strList.at(1);
- priv.manufacturer = strList.at(2);
- priv.serialNumber = strList.at(3);
- priv.productIdentifier = strList.at(4).toInt();
- priv.hasProductIdentifier = (priv.productIdentifier != BAD_PORT);
- priv.vendorIdentifier = strList.at(5).toInt();
- priv.hasVendorIdentifier = (priv.vendorIdentifier != BAD_PORT);
-
- (void) serialPortInfoList.append(priv);
+ bool pidOK, vidOK;
+ QSerialPortInfoPrivate info;
+ info.portName = QSerialPortInfoPrivate::portNameFromSystemLocation(strList[0]);
+ info.device = strList[0];
+ info.description = strList[1];
+ info.manufacturer = strList[2];
+ info.serialNumber = strList[3];
+ info.productIdentifier = strList[4].toInt(&pidOK);
+ info.hasProductIdentifier = (pidOK && (info.productIdentifier != INVALID_DEVICE_ID));
+ info.vendorIdentifier = strList[5].toInt(&vidOK);
+ info.hasVendorIdentifier = (vidOK && (info.vendorIdentifier != INVALID_DEVICE_ID));
+
+ serialPortInfoList.append(info);
}
+
+ env->DeleteLocalRef(objArray);
(void) env.checkAndClearExceptions();
return serialPortInfoList;
@@ -151,440 +265,883 @@ QList availableDevices()
int getDeviceId(const QString &portName)
{
- QJniObject name = QJniObject::fromString(portName);
-
- (void) AndroidInterface::cleanJavaException();
- const int result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getDeviceId",
- "(Ljava/lang/String;)I",
- name.object()
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ const QJniObject name = QJniObject::fromString(portName);
+ if (!name.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniObject for portName in getDeviceId";
+ return -1;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getDeviceId";
+ return -1;
+ }
+
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getDeviceId";
+ return -1;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getDeviceId", "(Ljava/lang/String;)I");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getDeviceId";
+ env.checkAndClearExceptions();
+ return -1;
+ }
+
+ const jint result = env->CallStaticIntMethod(javaClass, methodId, name.object());
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getDeviceId";
+ return -1;
+ }
+
+ return static_cast(result);
}
int getDeviceHandle(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const int result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getDeviceHandle",
- "(I)I",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getDeviceHandle";
+ return -1;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getDeviceHandle";
+ return -1;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getDeviceHandle", "(I)I");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getDeviceHandle";
+ env.checkAndClearExceptions();
+ return -1;
+ }
+
+ const jint result = env->CallStaticIntMethod(javaClass, methodId, static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getDeviceHandle";
+ return -1;
+ }
+
+ return static_cast(result);
}
int open(const QString &portName, QSerialPortPrivate *classPtr)
{
- QJniObject name = QJniObject::fromString(portName);
-
- (void) AndroidInterface::cleanJavaException();
- const int deviceId = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "open",
- "(Landroid/content/Context;Ljava/lang/String;J)I",
- QNativeInterface::QAndroidApplication::context(),
- name.object(),
- reinterpret_cast(classPtr)
- );
- (void) AndroidInterface::cleanJavaException();
-
- return deviceId;
+ if (!classPtr) {
+ qCWarning(AndroidSerialLog) << "open called with null serialPort";
+ return -1;
+ }
+
+ const QJniObject name = QJniObject::fromString(portName);
+ if (!name.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniObject for portName in open";
+ return -1;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in open";
+ return -1;
+ }
+
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in open";
+ return -1;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "open", "(Ljava/lang/String;J)I");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: open";
+ env.checkAndClearExceptions();
+ return -1;
+ }
+
+ const jint deviceId = env->CallStaticIntMethod(javaClass, methodId, name.object(), reinterpret_cast(classPtr));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling open";
+ return -1;
+ }
+
+ return static_cast(deviceId);
}
bool close(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "close",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in close";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in close";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "close", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: close";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling close";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool isOpen(const QString &portName)
{
- QJniObject name = QJniObject::fromString(portName);
-
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "isDeviceNameOpen",
- "(Ljava/lang/String;)Z",
- name.object()
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ const QJniObject name = QJniObject::fromString(portName);
+ if (!name.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniObject for portName in isOpen";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in isOpen";
+ return false;
+ }
+
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in isOpen";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "isDeviceNameOpen", "(Ljava/lang/String;)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: isDeviceNameOpen";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, name.object());
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling isDeviceNameOpen";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
QByteArray read(int deviceId, int length, int timeout)
{
- (void) AndroidInterface::cleanJavaException();
- const QJniObject result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "read",
- "(II)[B",
- deviceId,
- length,
- timeout
- );
- (void) AndroidInterface::cleanJavaException();
-
- jbyteArray jarray = result.object();
- QJniEnvironment jniEnv;
- jbyte* const bytes = jniEnv->GetByteArrayElements(jarray, nullptr);
- const jsize len = jniEnv->GetArrayLength(jarray);
- QByteArray data = QByteArray::fromRawData(reinterpret_cast(bytes), len);
- jniEnv->ReleaseByteArrayElements(jarray, bytes, JNI_ABORT);
- (void) jniEnv.checkAndClearExceptions();
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in read";
+ return QByteArray();
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in read";
+ return QByteArray();
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "read", "(III)[B");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: read";
+ env.checkAndClearExceptions();
+ return QByteArray();
+ }
+
+ const jbyteArray jarray = static_cast(env->CallStaticObjectMethod(javaClass, methodId,
+ static_cast(deviceId),
+ static_cast(length),
+ static_cast(timeout)));
+
+ if (!jarray) {
+ qCWarning(AndroidSerialLog) << "read method returned null";
+ (void) env.checkAndClearExceptions();
+ return QByteArray();
+ }
+
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling read";
+ env->DeleteLocalRef(jarray);
+ return QByteArray();
+ }
+
+ const jsize len = env->GetArrayLength(jarray);
+ jbyte *const bytes = env->GetByteArrayElements(jarray, nullptr);
+ if (!bytes) {
+ qCWarning(AndroidSerialLog) << "Failed to get byte array elements in read";
+ env->DeleteLocalRef(jarray);
+ return QByteArray();
+ }
+
+ const QByteArray data(reinterpret_cast(bytes), len);
+ env->ReleaseByteArrayElements(jarray, bytes, JNI_ABORT);
+ env->DeleteLocalRef(jarray);
return data;
}
-int write(int deviceId, QByteArrayView data, int length, int timeout, bool async)
+int write(int deviceId, const char *data, int length, int timeout, bool async)
{
- QJniEnvironment jniEnv;
- jbyteArray jarray = jniEnv->NewByteArray(static_cast(length));
- jniEnv->SetByteArrayRegion(jarray, 0, static_cast(length), reinterpret_cast(data.constData()));
+ if (!data || length <= 0) {
+ qCWarning(AndroidSerialLog) << "Invalid data or length in write";
+ return -1;
+ }
- (void) jniEnv.checkAndClearExceptions();
- timeout = qMax(0, timeout);
- int result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in write";
+ return -1;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in write";
+ return -1;
+ }
+
+ const jbyteArray jarray = env->NewByteArray(static_cast(length));
+ if (!jarray) {
+ qCWarning(AndroidSerialLog) << "Failed to create jbyteArray in write";
+ return -1;
+ }
+
+ env->SetByteArrayRegion(jarray, 0, static_cast(length), reinterpret_cast(data));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while setting byte array region in write";
+ env->DeleteLocalRef(jarray);
+ return -1;
+ }
+
+ jint result;
if (async) {
- result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "writeAsync",
- "(I[B)V",
- deviceId,
- jarray,
- timeout
- );
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "writeAsync", "(I[BI)I");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: writeAsync";
+ env.checkAndClearExceptions();
+ env->DeleteLocalRef(jarray);
+ return -1;
+ }
+
+ result = env->CallStaticIntMethod(javaClass, methodId,
+ static_cast(deviceId),
+ jarray,
+ static_cast(timeout));
} else {
- result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "write",
- "(I[BII)I",
- deviceId,
- jarray,
- length,
- timeout
- );
- }
- jniEnv->DeleteLocalRef(jarray);
- (void) jniEnv.checkAndClearExceptions();
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "write", "(I[BII)I");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: write";
+ env.checkAndClearExceptions();
+ env->DeleteLocalRef(jarray);
+ return -1;
+ }
+
+ result = env->CallStaticIntMethod(javaClass, methodId,
+ static_cast(deviceId),
+ jarray,
+ static_cast(length),
+ static_cast(timeout));
+ }
+
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling write/writeAsync";
+ env->DeleteLocalRef(jarray);
+ return -1;
+ }
+
+ env->DeleteLocalRef(jarray);
- return result;
+ return static_cast(result);
}
bool setParameters(int deviceId, int baudRate, int dataBits, int stopBits, int parity)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "setParameters",
- "(IIIII)Z",
- deviceId,
- baudRate,
- dataBits,
- stopBits,
- parity
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in setParameters";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in setParameters";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "setParameters", "(IIIII)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: setParameters";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId,
+ static_cast(deviceId),
+ static_cast(baudRate),
+ static_cast(dataBits),
+ static_cast(stopBits),
+ static_cast(parity));
+
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling setParameters";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool getCarrierDetect(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getCarrierDetect",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getCarrierDetect";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getCarrierDetect";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getCarrierDetect", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getCarrierDetect";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getCarrierDetect";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool getClearToSend(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getClearToSend",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getClearToSend";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getClearToSend";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getClearToSend", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getClearToSend";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getClearToSend";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool getDataSetReady(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getDataSetReady",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getDataSetReady";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getDataSetReady";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getDataSetReady", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getDataSetReady";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getDataSetReady";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool getDataTerminalReady(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getDataTerminalReady",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getDataTerminalReady";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getDataTerminalReady";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getDataTerminalReady", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getDataTerminalReady";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getDataTerminalReady";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool setDataTerminalReady(int deviceId, bool set)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "setDataTerminalReady",
- "(IZ)Z",
- deviceId,
- set
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in setDataTerminalReady";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in setDataTerminalReady";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "setDataTerminalReady", "(IZ)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: setDataTerminalReady";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean jSet = set ? JNI_TRUE : JNI_FALSE;
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, static_cast(deviceId), jSet);
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling setDataTerminalReady";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool getRingIndicator(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getRingIndicator",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getRingIndicator";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getRingIndicator";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getRingIndicator", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getRingIndicator";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getRingIndicator";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool getRequestToSend(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getRequestToSend",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getRequestToSend";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getRequestToSend";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getRequestToSend", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getRequestToSend";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getRequestToSend";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool setRequestToSend(int deviceId, bool set)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "setRequestToSend",
- "(IZ)Z",
- deviceId,
- set
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in setRequestToSend";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in setRequestToSend";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "setRequestToSend", "(IZ)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: setRequestToSend";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean jSet = set ? JNI_TRUE : JNI_FALSE;
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId, static_cast(deviceId), jSet);
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling setRequestToSend";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
QSerialPort::PinoutSignals getControlLines(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const QJniObject result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getControlLines",
- "(I)[I",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- jintArray jarray = result.object();
- QJniEnvironment jniEnv;
- jint* const ints = jniEnv->GetIntArrayElements(jarray, nullptr);
- const jsize len = jniEnv->GetArrayLength(jarray);
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getControlLines";
+ return QSerialPort::PinoutSignals();
+ }
- QSerialPort::PinoutSignals data = QSerialPort::PinoutSignals::fromInt(0);
- for (jsize i = 0; i < len; i++) {
- const jint value = ints[i];
- if ((value < ControlLine::RtsControlLine) || (value > ControlLine::RiControlLine)) {
- continue;
- }
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getControlLines";
+ return QSerialPort::PinoutSignals();
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getControlLines", "(I)[I");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getControlLines";
+ env.checkAndClearExceptions();
+ return QSerialPort::PinoutSignals();
+ }
+
+ const jintArray jarray = static_cast(env->CallStaticObjectMethod(javaClass, methodId, static_cast(deviceId)));
+ if (!jarray) {
+ qCWarning(AndroidSerialLog) << "getControlLines returned null";
+ (void) env.checkAndClearExceptions();
+ return QSerialPort::PinoutSignals();
+ }
- const ControlLine line = static_cast(value);
- switch (line) {
- case ControlLine::RtsControlLine:
- (void) data.setFlag(QSerialPort::PinoutSignal::RequestToSendSignal, true);
- break;
- case ControlLine::CtsControlLine:
- (void) data.setFlag(QSerialPort::PinoutSignal::ClearToSendSignal, true);
- break;
- case ControlLine::DtrControlLine:
- (void) data.setFlag(QSerialPort::PinoutSignal::DataTerminalReadySignal, true);
- break;
- case ControlLine::DsrControlLine:
- (void) data.setFlag(QSerialPort::PinoutSignal::DataSetReadySignal, true);
- break;
- case ControlLine::CdControlLine:
- (void) data.setFlag(QSerialPort::PinoutSignal::DataCarrierDetectSignal, true);
- break;
- case ControlLine::RiControlLine:
- (void) data.setFlag(QSerialPort::PinoutSignal::RingIndicatorSignal, true);
- break;
- default:
- break;
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getControlLines";
+ env->DeleteLocalRef(jarray);
+ return QSerialPort::PinoutSignals();
+ }
+
+ jint *const ints = env->GetIntArrayElements(jarray, nullptr);
+ if (!ints) {
+ qCWarning(AndroidSerialLog) << "Failed to get int array elements in getControlLines";
+ env->DeleteLocalRef(jarray);
+ return QSerialPort::PinoutSignals();
+ }
+
+ const jsize len = env->GetArrayLength(jarray);
+ QSerialPort::PinoutSignals data = QSerialPort::PinoutSignals();
+
+ for (jsize i = 0; i < len; ++i) {
+ const jint value = ints[i];
+ switch (value) {
+ case 0:
+ data |= QSerialPort::RequestToSendSignal;
+ break;
+ case 1: // CTS
+ data |= QSerialPort::ClearToSendSignal;
+ break;
+ case 2: // DTR
+ data |= QSerialPort::DataTerminalReadySignal;
+ break;
+ case 3: // DSR
+ data |= QSerialPort::DataSetReadySignal;
+ break;
+ case 4: // CD
+ data |= QSerialPort::DataCarrierDetectSignal;
+ break;
+ case 5: // RI
+ data |= QSerialPort::RingIndicatorSignal;
+ break;
+ default:
+ qCWarning(AndroidSerialLog) << "Unknown ControlLine value:" << value;
+ break;
}
}
- jniEnv->ReleaseIntArrayElements(jarray, ints, JNI_ABORT);
- (void) jniEnv.checkAndClearExceptions();
+
+ env->ReleaseIntArrayElements(jarray, ints, JNI_ABORT);
+ env->DeleteLocalRef(jarray);
+ env.checkAndClearExceptions();
return data;
}
int getFlowControl(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const int result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "getFlowControl",
- "(I)I",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return static_cast(result);
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in getFlowControl";
+ return QSerialPort::NoFlowControl;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in getFlowControl";
+ return QSerialPort::NoFlowControl;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "getFlowControl", "(I)I");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: getFlowControl";
+ env.checkAndClearExceptions();
+ return QSerialPort::NoFlowControl;
+ }
+
+ const jint flowControl = env->CallStaticIntMethod(javaClass, methodId, static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling getFlowControl";
+ return QSerialPort::NoFlowControl;
+ }
+
+ return static_cast(flowControl);
}
bool setFlowControl(int deviceId, int flowControl)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "setFlowControl",
- "(II)Z",
- deviceId,
- flowControl
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in setFlowControl";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in setFlowControl";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "setFlowControl", "(II)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: setFlowControl";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId,
+ static_cast(deviceId),
+ static_cast(flowControl));
+
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling setFlowControl";
+ return false;
+ }
+
+ return result == JNI_TRUE;
}
bool purgeBuffers(int deviceId, bool input, bool output)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "purgeBuffers",
- "(IZZ)Z",
- deviceId,
- input,
- output
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in purgeBuffers";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in purgeBuffers";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "purgeBuffers", "(IZZ)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: purgeBuffers";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean jInput = input ? JNI_TRUE : JNI_FALSE;
+ const jboolean jOutput = output ? JNI_TRUE : JNI_FALSE;
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId,
+ static_cast(deviceId),
+ jInput,
+ jOutput);
+
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling purgeBuffers";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool setBreak(int deviceId, bool set)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "setBreak",
- "(IZ)Z",
- deviceId,
- set
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in setBreak";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in setBreak";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "setBreak", "(IZ)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: setBreak";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean jSet = set ? JNI_TRUE : JNI_FALSE;
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId,
+ static_cast(deviceId),
+ jSet);
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling setBreak";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool startReadThread(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "startIoManager",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in startReadThread";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in startReadThread";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "startIoManager", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: startIoManager";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId,
+ static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling startIoManager";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool stopReadThread(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "stopIoManager",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in stopReadThread";
+ return false;
+ }
+
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in stopReadThread";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "stopIoManager", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: stopIoManager";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId,
+ static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling stopIoManager";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
bool readThreadRunning(int deviceId)
{
- (void) AndroidInterface::cleanJavaException();
- const bool result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "ioManagerRunning",
- "(I)Z",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
-}
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in readThreadRunning";
+ return false;
+ }
-int getReadBufferSize(int deviceId)
-{
- (void) AndroidInterface::cleanJavaException();
- const int result = QJniObject::callStaticMethod(
- AndroidInterface::getActivityClass(),
- "ioManagerReadBufferSize",
- "(I)I",
- deviceId
- );
- (void) AndroidInterface::cleanJavaException();
-
- return result;
+ const jclass javaClass = getSerialManagerClass();
+ if (!javaClass) {
+ qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in readThreadRunning";
+ return false;
+ }
+
+ const jmethodID methodId = env->GetStaticMethodID(javaClass, "ioManagerRunning", "(I)Z");
+ if (!methodId) {
+ qCWarning(AndroidSerialLog) << "Method Not Found: ioManagerRunning";
+ env.checkAndClearExceptions();
+ return false;
+ }
+
+ const jboolean result = env->CallStaticBooleanMethod(javaClass, methodId,
+ static_cast(deviceId));
+ if (env.checkAndClearExceptions()) {
+ qCWarning(AndroidSerialLog) << "Exception occurred while calling ioManagerRunning";
+ return false;
+ }
+
+ return (result == JNI_TRUE);
}
} // namespace AndroidSerial
diff --git a/android/src/AndroidSerial.h b/android/src/AndroidSerial.h
index ecd36f84ab0..d39c2f47d69 100644
--- a/android/src/AndroidSerial.h
+++ b/android/src/AndroidSerial.h
@@ -53,9 +53,12 @@ namespace AndroidSerial
XonXoffInlineFlowControl
};
- static constexpr char CHAR_XON = 17;
- static constexpr char CHAR_XOFF = 19;
+ constexpr char CHAR_XON = 17;
+ constexpr char CHAR_XOFF = 19;
+ constexpr const char *kJniUsbSerialManagerClassName = "org/mavlink/qgroundcontrol/QGCUsbSerialManager";
+
+ jclass getSerialManagerClass();
void setNativeMethods();
void jniDeviceHasDisconnected(JNIEnv *env, jobject obj, jlong classPtr);
void jniDeviceNewData(JNIEnv *env, jobject obj, jlong classPtr, jbyteArray data);
@@ -67,7 +70,7 @@ namespace AndroidSerial
bool close(int deviceId);
bool isOpen(const QString &portName);
QByteArray read(int deviceId, int length, int timeout);
- int write(int deviceId, QByteArrayView data, int length, int timeout, bool async);
+ int write(int deviceId, const char *data, int length, int timeout, bool async);
bool setParameters(int deviceId, int baudRate, int dataBits, int stopBits, int parity);
bool getCarrierDetect(int deviceId);
bool getClearToSend(int deviceId);
@@ -85,5 +88,4 @@ namespace AndroidSerial
bool startReadThread(int deviceId);
bool stopReadThread(int deviceId);
bool readThreadRunning(int deviceId);
- int getReadBufferSize(int deviceId);
-}; // namespace AndroidSerial
+} // namespace AndroidSerial
diff --git a/android/src/org/mavlink/qgroundcontrol/QGCActivity.java b/android/src/org/mavlink/qgroundcontrol/QGCActivity.java
index f470b11138f..7c2025aa8fe 100644
--- a/android/src/org/mavlink/qgroundcontrol/QGCActivity.java
+++ b/android/src/org/mavlink/qgroundcontrol/QGCActivity.java
@@ -1,195 +1,73 @@
package org.mavlink.qgroundcontrol;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Timer;
-import java.util.EnumSet;
-import java.util.TimerTask;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.bluetooth.BluetoothDevice;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbDeviceConnection;
-import android.hardware.usb.UsbManager;
-import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.PowerManager;
-import android.os.Process;
-import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
+import android.net.wifi.WifiManager;
import android.util.Log;
import android.view.WindowManager;
-// import org.freedesktop.gstreamer.GStreamer;
-
-import com.hoho.android.usbserial.driver.*;
-import com.hoho.android.usbserial.util.*;
-
import org.qtproject.qt.android.bindings.QtActivity;
-import org.qtproject.qt.android.QtNative;
-
-// TODO:
-// UsbSerialDriver getDriver();
-// UsbEndpoint getWriteEndpoint();
-// UsbEndpoint getReadEndpoint();
-// boolean getXON() throws IOException;
-// UsbAccessory, UsbConfiguration, UsbConstants, UsbDevice, UsbDeviceConnection, UsbEndpoint, UsbInterface, UsbManager, UsbRequest
-public class QGCActivity extends QtActivity
-{
+public class QGCActivity extends QtActivity {
private static final String TAG = QGCActivity.class.getSimpleName();
- private static final String SCREEN_BRIGHT_WAKE_LOCK_TAG = "QGroundControl"; // BuildConfig.LIBRARY_PACKAGE_NAME, Context.getPackageName()
- private static final String MULTICAST_LOCK_TAG = "QGroundControl"; // BuildConfig.LIBRARY_PACKAGE_NAME, Context.getPackageName()
+ private static final String SCREEN_BRIGHT_WAKE_LOCK_TAG = "QGroundControl";
+ private static final String MULTICAST_LOCK_TAG = "QGroundControl";
private static QGCActivity m_instance = null;
- private static Context m_context;
-
- private static final int BAD_DEVICE_ID = 0;
- private static final String ACTION_USB_PERMISSION = "org.mavlink.qgroundcontrol.action.USB_PERMISSION";
- private static PendingIntent m_usbPermissionIntent = null;
- private static UsbManager m_usbManager = null;
- private static List m_drivers;
- private static HashMap m_serialIoManagerHashByDeviceId;
- private static HashMap m_fileDescriptorHashByDeviceId;
- private static HashMap m_classPtrHashByDeviceId;
- private static PowerManager.WakeLock m_wakeLock;
- private static WifiManager.MulticastLock m_wifiMulticastLock;
-
- private final static BroadcastReceiver m_usbReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent)
- {
- final String action = intent.getAction();
- Log.i(TAG, "BroadcastReceiver USB action " + action);
-
- if (ACTION_USB_PERMISSION.equals(action)) {
- _handleUsbPermission(intent);
- } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
- _handleUsbDeviceDetached(intent);
- } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
- _handleUsbDeviceAttached(intent);
- }
-
- try {
- nativeUpdateAvailableJoysticks();
- } catch (final Exception ex) {
- Log.e(TAG, "Exception nativeUpdateAvailableJoysticks()", ex);
- }
- }
- };
-
- private static void _handleUsbPermission(final Intent intent)
- {
- synchronized (m_instance) {
- final UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
- if (device != null) {
- if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
- Log.i(TAG, "Permission granted to " + device.getDeviceName());
- } else {
- Log.i(TAG, "Permission denied for " + device.getDeviceName());
- }
- }
- }
- }
+ private PowerManager.WakeLock m_wakeLock;
+ private WifiManager.MulticastLock m_wifiMulticastLock;
- private static void _handleUsbDeviceDetached(final Intent intent)
- {
- final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
- if (device != null) {
- if (m_classPtrHashByDeviceId.containsKey(device.getDeviceId())) {
- nativeDeviceHasDisconnected(m_classPtrHashByDeviceId.get(device.getDeviceId()));
- }
- }
- }
-
- private static void _handleUsbDeviceAttached(final Intent intent)
- {
- final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
- if (device != null) {
- final UsbSerialDriver driver = _findDriverByDeviceId(device.getDeviceId());
- if ((driver != null) && !m_usbManager.hasPermission(device)) {
- Log.i(TAG, "Requesting permission for device " + device.getDeviceName());
- m_usbManager.requestPermission(device, m_usbPermissionIntent);
- }
- }
- }
-
- // Native C++ functions which connect back to QSerialPort code
- private static native void nativeDeviceHasDisconnected(final long classPtr);
- public static native void nativeDeviceException(final long classPtr, final String message);
- public static native void nativeDeviceNewData(final long classPtr, final byte[] data);
- private static native void nativeUpdateAvailableJoysticks();
-
- // Native C++ functions called to log output
- public static native void qgcLogDebug(final String message);
- public static native void qgcLogWarning(final String message);
-
- public native void nativeInit();
-
- // QGCActivity singleton
- public QGCActivity()
- {
+ public QGCActivity() {
m_instance = this;
- m_drivers = new ArrayList<>();
- m_classPtrHashByDeviceId = new HashMap<>();
- m_serialIoManagerHashByDeviceId = new HashMap<>();
- m_fileDescriptorHashByDeviceId = new HashMap<>();
}
- public void onInit(int status)
- {
- // System.loadLibrary("gstreamer_android");
+ /**
+ * Returns the singleton instance of QGCActivity.
+ *
+ * @return The current instance of QGCActivity.
+ */
+ public static QGCActivity getInstance() {
+ return m_instance;
}
@Override
- public void onCreate(Bundle savedInstanceState)
- {
+ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nativeInit();
- _acquireWakeLock();
- _keepScreenOn();
- _setupMulticastLock();
- _registerReceivers();
- _setupUsbPermissionIntent();
-
- m_usbManager = (UsbManager) m_instance.getSystemService(Context.USB_SERVICE);
+ acquireWakeLock();
+ keepScreenOn();
+ setupMulticastLock();
- // try {
- // GStreamer.init(this);
- // } catch (final Exception ex) {
- // Log.e(TAG, "Exception GStreamer.init(this)", ex);
- // }
+ QGCUsbSerialManager.initialize(this);
}
@Override
- protected void onDestroy()
- {
+ protected void onDestroy() {
try {
- _releaseMulticastLock();
- _releaseWakeLock();
- } catch(final Exception e) {
- Log.e(TAG, "Exception onDestroy()", e);
+ releaseMulticastLock();
+ releaseWakeLock();
+ QGCUsbSerialManager.cleanup(this);
+ } catch (final Exception e) {
+ Log.e(TAG, "Exception onDestroy()", e);
}
super.onDestroy();
}
- private void _keepScreenOn()
- {
+ /**
+ * Keeps the screen on by adding the appropriate window flag.
+ */
+ private void keepScreenOn() {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
- private void _acquireWakeLock()
- {
+ /**
+ * Acquires a wake lock to keep the CPU running.
+ */
+ private void acquireWakeLock() {
final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
m_wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, SCREEN_BRIGHT_WAKE_LOCK_TAG);
if (m_wakeLock != null) {
@@ -199,18 +77,21 @@ private void _acquireWakeLock()
}
}
- private void _releaseWakeLock()
- {
- if (m_wakeLock != null) {
+ /**
+ * Releases the wake lock if held.
+ */
+ private void releaseWakeLock() {
+ if (m_wakeLock != null && m_wakeLock.isHeld()) {
m_wakeLock.release();
}
}
- // Workaround for QTBUG-73138
- private void _setupMulticastLock()
- {
+ /**
+ * Sets up a multicast lock to allow multicast packets.
+ */
+ private void setupMulticastLock() {
if (m_wifiMulticastLock == null) {
- final WifiManager wifi = (WifiManager)getSystemService(Context.WIFI_SERVICE);
+ final WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
m_wifiMulticastLock = wifi.createMulticastLock(MULTICAST_LOCK_TAG);
m_wifiMulticastLock.setReferenceCounted(true);
}
@@ -219,1017 +100,18 @@ private void _setupMulticastLock()
Log.d(TAG, "Multicast lock: " + m_wifiMulticastLock.toString());
}
- private void _releaseMulticastLock()
- {
- if (m_wifiMulticastLock != null) {
- m_wifiMulticastLock.release();
- Log.d(TAG, "Multicast lock released.");
- }
- }
-
- private void _registerReceivers()
- {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
- filter.addAction(ACTION_USB_PERMISSION);
- filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
- filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
- registerReceiver(m_usbReceiver, filter, RECEIVER_EXPORTED);
- } else {
- registerReceiver(m_usbReceiver, filter);
- }
- }
-
- private void _setupUsbPermissionIntent()
- {
- int intentFlags = 0;
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
- intentFlags = PendingIntent.FLAG_IMMUTABLE;
- }
- m_usbPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), intentFlags);
- }
-
- private static UsbSerialDriver _findDriverByDeviceId(int deviceId)
- {
- for (final UsbSerialDriver driver: m_drivers) {
- if (driver.getDevice().getDeviceId() == deviceId) {
- return driver;
- }
- }
-
- return null;
- }
-
- private static UsbSerialDriver _findDriverByDeviceName(final String deviceName)
- {
- for (final UsbSerialDriver driver: m_drivers) {
- if (driver.getDevice().getDeviceName().equals(deviceName)) {
- return driver;
- }
- }
-
- return null;
- }
-
- private static UsbSerialPort _findPortByDeviceId(int deviceId)
- {
- final UsbSerialDriver driver = _findDriverByDeviceId(deviceId);
- if (driver == null) {
- return null;
- }
-
- final List ports = driver.getPorts();
- if (ports.isEmpty()) {
- Log.w(TAG, "No ports available on device ID " + deviceId);
- return null;
- }
-
- final UsbSerialPort port = ports.get(0);
- return port;
- }
-
- /*private static void _refreshUsbDevices()
- {
- final UsbSerialProber usbDefaultProber = UsbSerialProber.getDefaultProber();
- final UsbSerialProber usbQGCProber = QGCProber.getQGCProber();
-
- m_drivers.clear();
- for (final UsbDevice device : m_usbManager.getDeviceList().values()) {
- UsbSerialDriver driver = usbDefaultProber.probeDevice(device);
- if (driver == null) {
- driver = usbQGCProber.probeDevice(device);
- }
- if (driver != null) {
- m_drivers.add(driver);
- }
- }
- }*/
-
/**
- * @brief Incrementally updates the list of drivers connected to the device.
+ * Releases the multicast lock if held.
*/
- private static void _updateCurrentDrivers()
- {
- final UsbSerialProber usbDefaultProber = UsbSerialProber.getDefaultProber();
- List currentDrivers = usbDefaultProber.findAllDrivers(m_usbManager);
- if (currentDrivers.isEmpty()) {
- currentDrivers = QGCProber.getQGCProber().findAllDrivers(m_usbManager);
- }
-
- if (currentDrivers.isEmpty()) {
- return;
- }
-
- _removeStaleDrivers(currentDrivers);
-
- _addNewDrivers(currentDrivers);
- }
-
- private static void _removeStaleDrivers(final List currentDrivers)
- {
- for (int i = m_drivers.size() - 1; i >= 0; i--) {
- boolean found = false;
- for (final UsbSerialDriver currentDriver : currentDrivers) {
- if (m_drivers.get(i).getDevice().getDeviceId() == currentDriver.getDevice().getDeviceId()) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- Log.i(TAG, "Remove stale driver " + m_drivers.get(i).getDevice().getDeviceName());
- m_drivers.remove(i);
- }
- }
- }
-
- private static void _addNewDrivers(final List currentDrivers)
- {
- for (final UsbSerialDriver newDriver : currentDrivers) {
- boolean found = false;
- for (final UsbSerialDriver driver : m_drivers) {
- if (newDriver.getDevice().getDeviceId() == driver.getDevice().getDeviceId()) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- _addDriver(newDriver);
- }
- }
- }
-
- private static void _addDriver(final UsbSerialDriver newDriver)
- {
- final UsbDevice device = newDriver.getDevice();
- final String deviceName = device.getDeviceName();
-
- m_drivers.add(newDriver);
- Log.i(TAG, "Adding new driver " + deviceName);
-
- if (m_usbManager.hasPermission(device)) {
- Log.i(TAG, "Already have permission to use device " + deviceName);
- } else {
- Log.i(TAG, "Requesting permission to use device " + deviceName);
- m_usbManager.requestPermission(device, m_usbPermissionIntent);
- }
- }
-
- /**
- * @brief Returns an array of device info for each device.
- *
- * @return String[] Device info in the format "DeviceName:ProductName:Manufacturer:SerialNumber:ProductId:VendorId".
- */
- public static String[] availableDevicesInfo()
- {
- _updateCurrentDrivers();
-
- if (m_usbManager.getDeviceList().size() < 1) {
- return null;
- }
-
- final List deviceInfoList = new ArrayList<>();
-
- for (final UsbDevice device : m_usbManager.getDeviceList().values()) {
- final String deviceInfo = _formatDeviceInfo(device);
- deviceInfoList.add(deviceInfo);
- }
-
- return deviceInfoList.toArray(new String[0]);
- }
-
- private static String _formatDeviceInfo(final UsbDevice device)
- {
- String deviceInfo = "";
- deviceInfo += device.getDeviceName() + ":";
- deviceInfo += device.getProductName() + ":";
- deviceInfo += device.getManufacturerName() + ":";
- deviceInfo += device.getSerialNumber() + ":";
- deviceInfo += device.getProductId() + ":";
- deviceInfo += device.getVendorId() + ":";
-
- Log.i(TAG, "_formatDeviceInfo " + deviceInfo);
-
- return deviceInfo;
- }
-
- /**
- * @brief Opens the specified device.
- *
- * @param classPtr Data to associate with the device and pass back through to native calls.
- * @return int Device ID.
- */
- public static int open(final Context parentContext, final String deviceName, final long classPtr)
- {
- m_context = parentContext;
-
- final UsbSerialDriver driver = _findDriverByDeviceName(deviceName);
- if (driver == null) {
- Log.w(TAG, "Attempt to open unknown device " + deviceName);
- return BAD_DEVICE_ID;
- }
-
- final List ports = driver.getPorts();
- if (ports.isEmpty()) {
- Log.w(TAG, "No ports available on device " + deviceName);
- return BAD_DEVICE_ID;
- }
-
- final UsbSerialPort port = ports.get(0);
-
- final UsbDevice device = driver.getDevice();
- final int deviceId = device.getDeviceId();
-
- if (!_openDriver(port, device, deviceId, classPtr)) {
- return BAD_DEVICE_ID;
- }
-
- return deviceId;
- }
-
- private static boolean _openDriver(final UsbSerialPort port, final UsbDevice device, final int deviceId, final long classPtr)
- {
- final UsbDeviceConnection connection = m_usbManager.openDevice(device);
- if (connection == null) {
- Log.w(TAG, "No Usb Device Connection");
- // TODO: add UsbManager.requestPermission(driver.getDevice(), ..) handling here?
- return false;
- }
-
- try {
- port.open(connection);
- } catch (final IOException ex) {
- Log.e(TAG, "Error opening driver", ex);
- return false;
- }
-
- if (!setParameters(deviceId, 115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)) {
- return false;
- }
-
- if (!_createIoManager(deviceId, port, classPtr)) {
- return false;
- }
-
- m_classPtrHashByDeviceId.put(deviceId, classPtr);
- m_fileDescriptorHashByDeviceId.put(deviceId, connection.getFileDescriptor());
-
- /*final ArrayList descriptors = UsbUtils.getDescriptors(connection);
- for (byte[] descriptor : descriptors) {
- Log.d(TAG, "Descriptor: " + HexDump.toHexString(descriptor));
- }*/
-
- Log.d(TAG, "Port open successful");
- return true;
- }
-
- private static boolean _createIoManager(final int deviceId, final UsbSerialPort port, final long classPtr)
- {
- if (m_serialIoManagerHashByDeviceId.get(deviceId) != null) {
- return true;
- }
-
- final QGCSerialListener listener = new QGCSerialListener(classPtr);
- final SerialInputOutputManager ioManager = new SerialInputOutputManager(port, listener);
-
- // ioManager.setReadBufferSize(4096);
- // ioManager.setWriteBufferSize(4096);
-
- Log.d(TAG, "Read Buffer Size: " + ioManager.getReadBufferSize());
- Log.d(TAG, "Write Buffer Size: " + ioManager.getWriteBufferSize());
-
- try {
- ioManager.setReadTimeout(0);
- } catch (final IllegalStateException e) {
- Log.e(TAG, "Set Read Timeout exception:", e);
- return false;
- }
-
- ioManager.setWriteTimeout(0);
-
- try {
- ioManager.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
- } catch (final IllegalStateException e) {
- Log.e(TAG, "Set Thread Priority exception:", e);
- }
-
- m_serialIoManagerHashByDeviceId.put(deviceId, ioManager);
-
- return true;
- }
-
- public static boolean startIoManager(final int deviceId)
- {
- if (ioManagerRunning(deviceId)) {
- return true;
- }
-
- final SerialInputOutputManager ioManager = m_serialIoManagerHashByDeviceId.get(deviceId);
- if (ioManager == null) {
- return false;
- }
-
- try {
- ioManager.start();
- } catch (final IllegalStateException e) {
- Log.e(TAG, "IO Manager Start exception:", e);
- return false;
- }
-
- return true;
- }
-
- public static boolean stopIoManager(final int deviceId)
- {
- final SerialInputOutputManager ioManager = m_serialIoManagerHashByDeviceId.get(deviceId);
- if (ioManager == null) {
- return false;
- }
-
- final SerialInputOutputManager.State ioState = ioManager.getState();
- if (ioState == SerialInputOutputManager.State.STOPPED || ioState == SerialInputOutputManager.State.STOPPING) {
- return true;
- }
-
- if (ioManagerRunning(deviceId)) {
- ioManager.stop();
- }
-
- return true;
- }
-
- public static boolean ioManagerRunning(final int deviceId)
- {
- final SerialInputOutputManager ioManager = m_serialIoManagerHashByDeviceId.get(deviceId);
- if (ioManager == null) {
- return false;
- }
-
- final SerialInputOutputManager.State ioState = ioManager.getState();
- return (ioState == SerialInputOutputManager.State.RUNNING);
- }
-
- public static int ioManagerReadBufferSize(final int deviceId)
- {
- final SerialInputOutputManager ioManager = m_serialIoManagerHashByDeviceId.get(deviceId);
- if (ioManager == null) {
- return 0;
- }
-
- return ioManager.getReadBufferSize();
- }
-
- /**
- * @brief Closes the device.
- *
- * @param deviceId ID number from the open command.
- * @return true if the operation is successful, false otherwise.
- */
- public static boolean close(int deviceId)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- try {
- port.close();
- } catch (final IOException ex) {
- Log.e(TAG, "Error closing driver:", ex);
- return false;
- }
-
- if (stopIoManager(deviceId)) {
- m_serialIoManagerHashByDeviceId.remove(deviceId);
- }
-
- m_classPtrHashByDeviceId.remove(deviceId);
- m_fileDescriptorHashByDeviceId.remove(deviceId);
-
- return true;
- }
-
- /**
- * @brief Writes data to the device.
- *
- * @param deviceId ID number from the open command.
- * @param data Byte array of data to write.
- * @param timeoutMsec Amount of time in milliseconds to wait for the write to occur.
- * @return int Number of bytes written.
- */
- public static int write(final int deviceId, final byte[] data, final int length, final int timeoutMSec)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return -1;
- }
-
- try {
- port.write(data, length, timeoutMSec);
- } catch (final SerialTimeoutException e) {
- Log.e(TAG, "Write timeout occurred", e);
- return -1;
- } catch (final IOException e) {
- Log.e(TAG, "Error writing data", e);
- return -1;
- }
-
- return length;
- }
-
- /**
- * Writes data to the device asynchronously.
- *
- * @param deviceId ID number from the open command.
- * @param data Byte array of data to write.
- */
- public static int writeAsync(final int deviceId, final byte[] data, final int timeoutMSec)
- {
- final SerialInputOutputManager ioManager = m_serialIoManagerHashByDeviceId.get(deviceId);
- if (ioManager == null) {
- Log.w(TAG, "IO Manager not found for device ID " + deviceId);
- return -1;
- }
-
- if (ioManager.getReadTimeout() == 0) {
- return -1;
- }
-
- ioManager.setWriteTimeout(timeoutMSec);
-
- ioManager.writeAsync(data);
-
- return data.length;
- }
-
- public static byte[] read(final int deviceId, final int length, final int timeoutMs)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return new byte[] {};
- }
-
- byte[] buffer = new byte[length];
- int bytesRead = 0;
-
- try {
- // TODO: Use bytesRead
- bytesRead = port.read(buffer, timeoutMs);
- } catch (final IOException e) {
- Log.e(TAG, "Error reading data", e);
- }
-
- return buffer;
- }
-
- public static boolean isDeviceNameValid(final String name)
- {
- for (final UsbSerialDriver driver: m_drivers) {
- if (driver.getDevice().getDeviceName().equals(name)) {
- return true;
- }
- }
-
- return false;
- }
-
- public static boolean isDeviceNameOpen(final String name)
- {
- for (final UsbSerialDriver driver: m_drivers) {
- final List ports = driver.getPorts();
- if (!ports.isEmpty() && name.equals(driver.getDevice().getDeviceName()) && ports.get(0).isOpen()) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * @brief Get Device ID.
- *
- * @param deviceName Device Name.
- * @return int Device ID.
- */
- public static int getDeviceId(final String deviceName)
- {
- final UsbSerialDriver driver = _findDriverByDeviceName(deviceName);
- if (driver == null) {
- Log.w(TAG, "Attempt to open unknown device " + deviceName);
- return BAD_DEVICE_ID;
- }
-
- final UsbDevice device = driver.getDevice();
- final int deviceId = device.getDeviceId();
-
- return deviceId;
- }
-
- /**
- * @brief Sets the parameters on an open port.
- *
- * @param deviceId ID number from the open command.
- * @param baudRate Decimal value of the baud rate. For example, 9600, 57600, 115200, etc.
- * @param dataBits Number of data bits. Valid numbers are 5, 6, 7, 8.
- * @param stopBits Number of stop bits. Valid numbers are 1, 2.
- * @param parity Parity setting: No Parity=0, Odd Parity=1, Even Parity=2.
- * @return true if the operation is successful, false otherwise.
- */
- public static boolean setParameters(final int deviceId, final int baudRate, final int dataBits, final int stopBits, final int parity)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- try {
- port.setParameters(baudRate, dataBits, stopBits, parity);
- } catch (final IOException e) {
- Log.e(TAG, "Error setting parameters", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error setting parameters", e);
- return false;
- }
-
- return true;
- }
-
- private static boolean _isControlLineSupported(final UsbSerialPort port, final UsbSerialPort.ControlLine controlLine)
- {
- EnumSet supportedControlLines;
-
- try {
- supportedControlLines = port.getSupportedControlLines();
- } catch (final IOException e) {
- Log.e(TAG, "Error getting supported control lines", e);
- return false;
- }
-
- return supportedControlLines.contains(controlLine);
- }
-
- /**
- * @brief Gets the Carrier Detect (CD) flag.
- *
- * @param deviceId ID number from the open command.
- */
- public static boolean getCarrierDetect(final int deviceId)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- if (!_isControlLineSupported(port, UsbSerialPort.ControlLine.CD)) {
- Log.e(TAG, "Getting CD Not Supported");
- return false;
- }
-
- boolean cd;
-
- try {
- cd = port.getCD();
- } catch (final IOException e) {
- Log.e(TAG, "Error getting CD", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error getting CD", e);
- return false;
- }
-
- return cd;
- }
-
- /**
- * @brief Gets the Clear To Send (CTS) flag.
- *
- * @param deviceId ID number from the open command.
- */
- public static boolean getClearToSend(final int deviceId)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- if (!_isControlLineSupported(port, UsbSerialPort.ControlLine.CTS)) {
- Log.e(TAG, "Getting CTS Not Supported");
- return false;
- }
-
- boolean cts;
-
- try {
- cts = port.getCTS();
- } catch (final IOException e) {
- Log.e(TAG, "Error getting CTS", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error getting CTS", e);
- return false;
- }
-
- return cts;
- }
-
- /**
- * @brief Gets the Data Set Ready (DSR) flag.
- *
- * @param deviceId ID number from the open command.
- */
- public static boolean getDataSetReady(final int deviceId)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- if (!_isControlLineSupported(port, UsbSerialPort.ControlLine.DSR)) {
- Log.e(TAG, "Getting DSR Not Supported");
- return false;
- }
-
- boolean dsr;
-
- try {
- dsr = port.getDSR();
- } catch (final IOException e) {
- Log.e(TAG, "Error getting DSR", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error getting DSR", e);
- return false;
- }
-
- return dsr;
- }
-
- /**
- * @brief Gets the Data Terminal Ready (DTR) flag.
- *
- * @param deviceId ID number from the open command.
- */
- public static boolean getDataTerminalReady(final int deviceId)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- if (!_isControlLineSupported(port, UsbSerialPort.ControlLine.DTR)) {
- Log.e(TAG, "Getting DTR Not Supported");
- return false;
- }
-
- boolean dtr;
-
- try {
- dtr = port.getDTR();
- } catch (final IOException e) {
- Log.e(TAG, "Error getting DTR", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error getting DTR", e);
- return false;
- }
-
- return dtr;
- }
-
- /**
- * @brief Sets the Data Terminal Ready (DTR) flag on the device.
- *
- * @param deviceId ID number from the open command.
- * @param on true to turn on, false to turn off.
- * @return true if the operation is successful, false otherwise.
- */
- public static boolean setDataTerminalReady(final int deviceId, final boolean on)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- if (!_isControlLineSupported(port, UsbSerialPort.ControlLine.DTR)) {
- Log.e(TAG, "Setting DTR Not Supported");
- return false;
- }
-
- try {
- port.setDTR(on);
- } catch (final IOException e) {
- Log.e(TAG, "Error setting DTR", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error setting DTR", e);
- return false;
- }
-
- return true;
- }
-
- /**
- * @brief Gets the Ring Indicator (RI) flag.
- *
- * @param deviceId ID number from the open command.
- */
- public static boolean getRingIndicator(final int deviceId)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- if (!_isControlLineSupported(port, UsbSerialPort.ControlLine.RI)) {
- Log.e(TAG, "Getting RI Not Supported");
- return false;
- }
-
- boolean ri;
-
- try {
- ri = port.getRI();
- } catch (final IOException e) {
- Log.e(TAG, "Error getting RI", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error getting RI", e);
- return false;
- }
-
- return ri;
- }
-
- /**
- * @brief Gets the Request to Send (RTS) flag.
- *
- * @param deviceId ID number from the open command.
- */
- public static boolean getRequestToSend(final int deviceId)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- if (!_isControlLineSupported(port, UsbSerialPort.ControlLine.RTS)) {
- Log.e(TAG, "Getting RTS Not Supported");
- return false;
- }
-
- boolean rts;
-
- try {
- rts = port.getRTS();
- } catch (final IOException e) {
- Log.e(TAG, "Error getting RTS", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error getting RTS", e);
- return false;
- }
-
- return rts;
- }
-
- /**
- * @brief Sets the Request to Send (RTS) flag.
- *
- * @param deviceId ID number from the open command.
- * @param on true to turn on, false to turn off.
- * @return true if the operation is successful, false otherwise.
- */
- public static boolean setRequestToSend(final int deviceId, final boolean on)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- if (!_isControlLineSupported(port, UsbSerialPort.ControlLine.RTS)) {
- Log.e(TAG, "Setting RTS Not Supported");
- return false;
- }
-
- try {
- port.setRTS(on);
- } catch (final IOException e) {
- Log.e(TAG, "Error setting RTS", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error setting RTS", e);
- return false;
- }
-
- return true;
- }
-
- /**
- * @brief Gets the Control Lines flags.
- *
- * @param deviceId ID number from the open command.
- */
- public static int[] getControlLines(final int deviceId)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return new int[] {};
- }
-
- EnumSet supportedControlLines;
-
- try {
- supportedControlLines = port.getSupportedControlLines();
- } catch (final IOException e) {
- Log.e(TAG, "Error getting supported control lines", e);
- return new int[] {};
- }
-
- if (supportedControlLines.isEmpty()) {
- Log.e(TAG, "Control Lines Not Supported");
- return new int[] {};
- }
-
- EnumSet controlLines;
-
- try {
- controlLines = port.getControlLines();
- } catch (final IOException e) {
- Log.e(TAG, "Error getting RTS", e);
- return new int[] {};
- }
-
- return controlLines.stream().mapToInt(UsbSerialPort.ControlLine::ordinal).toArray();
- }
-
- /**
- * @brief Gets the Flow Control type.
- *
- * @param deviceId ID number from the open command.
- */
- public static int getFlowControl(final int deviceId)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return 0;
- }
-
- final EnumSet supportedFlowControl = port.getSupportedFlowControl();
-
- if (supportedFlowControl.isEmpty()) {
- Log.e(TAG, "Flow Control Not Supported");
- return 0;
- }
-
- final UsbSerialPort.FlowControl flowControl = port.getFlowControl();
-
- return flowControl.ordinal();
- }
-
- /**
- * @brief Sets the Flow Control flag.
- *
- * @param deviceId ID number from the open command.
- */
- public static boolean setFlowControl(final int deviceId, final int flowControl)
- {
- if (getFlowControl(deviceId) == flowControl) {
- return true;
- }
-
- if ((flowControl < 0) || (flowControl >= UsbSerialPort.FlowControl.values().length)) {
- Log.w(TAG, "Invalid flow control ordinal " + flowControl);
- return false;
- }
-
- final UsbSerialPort.FlowControl flowControlEnum = UsbSerialPort.FlowControl.values()[flowControl];
-
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- final EnumSet supportedFlowControl = port.getSupportedFlowControl();
-
- if (!supportedFlowControl.contains(flowControlEnum)) {
- Log.e(TAG, "Setting Flow Control Not Supported");
- return false;
- }
-
- // TODO: This shouldn't be necessary but an UnsupportedOperationException is thrown for NONE
- // if ((flowControlEnum == UsbSerialPort.FlowControl.NONE) && (supportedFlowControl.size() == 1)) {
- // return true;
- // }
-
- try {
- port.setFlowControl(flowControlEnum);
- } catch (final IOException e) {
- Log.e(TAG, "Error setting Flow Control", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error setting Flow Control", e);
- return false;
- }
-
- return true;
- }
-
- /**
- * Sets the break condition on the device.
- *
- * @param deviceId ID number from the open command.
- * @param on true to set break, false to clear break.
- * @return true if the operation is successful, false otherwise.
- */
- public static boolean setBreak(final int deviceId, final boolean on)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- try {
- port.setBreak(on);
- } catch (final IOException e) {
- Log.e(TAG, "Error setting break condition", e);
- return false;
- }
-
- return true;
- }
-
- /**
- * @brief Purges the hardware buffers based on the input and output flags.
- *
- * @param deviceId ID number from the open command.
- * @param input true to purge the input buffer.
- * @param output true to purge the output buffer.
- * @return true if the operation is successful, false otherwise.
- */
- public static boolean purgeBuffers(final int deviceId, final boolean input, final boolean output)
- {
- final UsbSerialPort port = _findPortByDeviceId(deviceId);
- if (port == null) {
- return false;
- }
-
- try {
- port.purgeHwBuffers(input, output);
- } catch (final IOException e) {
- Log.e(TAG, "Error purging buffers", e);
- return false;
- } catch (final UnsupportedOperationException e) {
- Log.e(TAG, "Error purging buffers", e);
- return false;
- }
-
- return true;
- }
-
- /**
- * @brief Gets the native device handle (file descriptor).
- *
- * @param deviceId ID number from the open command.
- * @return int Device handle.
- */
- public static int getDeviceHandle(final int deviceId)
- {
- return m_fileDescriptorHashByDeviceId.getOrDefault(deviceId, -1);
- }
-
- public static String getSDCardPath()
- {
- final StorageManager storageManager = (StorageManager) m_instance.getSystemService(Activity.STORAGE_SERVICE);
- final List volumes = storageManager.getStorageVolumes();
- for (final StorageVolume vol : volumes) {
- final String path = getStorageVolumePath(vol);
- if (path != null) {
- return path;
- }
+ private void releaseMulticastLock() {
+ if (m_wifiMulticastLock != null && m_wifiMulticastLock.isHeld()) {
+ m_wifiMulticastLock.release();
+ Log.d(TAG, "Multicast lock released.");
}
-
- return "";
}
- private static String getStorageVolumePath(final StorageVolume vol)
- {
- try {
- final Method methodGetPath = vol.getClass().getMethod("getPath");
- final String path = (String) methodGetPath.invoke(vol);
- if (vol.isRemovable()) {
- Log.i(TAG, "removable sd card mounted " + path);
- return path;
- } else {
- Log.i(TAG, "storage mounted " + path);
- }
- } catch (final Exception e) {
- Log.e(TAG, "Error getting storage volume path", e);
- }
-
- return null;
- }
+ // Native C++ functions
+ public native boolean nativeInit();
+ public native void qgcLogDebug(final String message);
+ public native void qgcLogWarning(final String message);
}
diff --git a/android/src/org/mavlink/qgroundcontrol/QGCLogger.java b/android/src/org/mavlink/qgroundcontrol/QGCLogger.java
new file mode 100644
index 00000000000..6ad7779567c
--- /dev/null
+++ b/android/src/org/mavlink/qgroundcontrol/QGCLogger.java
@@ -0,0 +1,65 @@
+package org.mavlink.qgroundcontrol;
+
+import android.util.Log;
+
+/**
+ * A centralized logging utility that manages log messages across the application.
+ * It controls log levels and formats based on build configurations.
+ */
+public class QGCLogger {
+ // Determine if the build is a debug build
+ private static final boolean DEBUG = BuildConfig.DEBUG;
+
+ /**
+ * Logs a debug message.
+ *
+ * @param tag The source of the log message.
+ * @param message The message to log.
+ */
+ public static void d(String tag, String message) {
+ if (DEBUG) {
+ Log.d(tag, message);
+ }
+ }
+
+ /**
+ * Logs an informational message.
+ *
+ * @param tag The source of the log message.
+ * @param message The message to log.
+ */
+ public static void i(String tag, String message) {
+ Log.i(tag, message);
+ }
+
+ /**
+ * Logs a warning message.
+ *
+ * @param tag The source of the log message.
+ * @param message The message to log.
+ */
+ public static void w(String tag, String message) {
+ Log.w(tag, message);
+ }
+
+ /**
+ * Logs an error message along with a throwable.
+ *
+ * @param tag The source of the log message.
+ * @param message The message to log.
+ * @param throwable The throwable to log.
+ */
+ public static void e(String tag, String message, Throwable throwable) {
+ Log.e(tag, message, throwable);
+ }
+
+ /**
+ * Logs an error message without a throwable.
+ *
+ * @param tag The source of the log message.
+ * @param message The message to log.
+ */
+ public static void e(String tag, String message) {
+ Log.e(tag, message);
+ }
+}
diff --git a/android/src/org/mavlink/qgroundcontrol/QGCProber.java b/android/src/org/mavlink/qgroundcontrol/QGCProber.java
index d85164ea4db..6c8b640a327 100644
--- a/android/src/org/mavlink/qgroundcontrol/QGCProber.java
+++ b/android/src/org/mavlink/qgroundcontrol/QGCProber.java
@@ -5,12 +5,15 @@
import com.hoho.android.usbserial.driver.ProbeTable;
import com.hoho.android.usbserial.driver.UsbSerialProber;
-class QGCProber
+public class QGCProber
{
- static UsbSerialProber getQGCProber()
- {
- final ProbeTable customTable = new ProbeTable();
+ public static UsbSerialProber getQGCProber() {
+ return new UsbSerialProber(getQGCProbeTable());
+ }
+
+ public static ProbeTable getQGCProbeTable() {
+ final ProbeTable probeTable = new ProbeTable();
- return new UsbSerialProber(customTable);
+ return probeTable;
}
}
diff --git a/android/src/org/mavlink/qgroundcontrol/QGCSerialListener.java b/android/src/org/mavlink/qgroundcontrol/QGCSerialListener.java
deleted file mode 100644
index 85399c54c6f..00000000000
--- a/android/src/org/mavlink/qgroundcontrol/QGCSerialListener.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.mavlink.qgroundcontrol;
-
-import android.util.Log;
-
-import com.hoho.android.usbserial.util.SerialInputOutputManager;
-
-public class QGCSerialListener implements SerialInputOutputManager.Listener
-{
- private final static String TAG = QGCSerialListener.class.getSimpleName();
-
- private long classPtr;
-
- QGCSerialListener(long classPtr)
- {
- this.classPtr = classPtr;
- }
-
- @Override
- public void onNewData(byte[] data)
- {
- // Log.i(TAG, "Serial read:" + new String(data, StandardCharsets.UTF_8));
- QGCActivity.nativeDeviceNewData(classPtr, data);
- }
-
- @Override
- public void onRunError(Exception ex)
- {
- Log.e(TAG, "onRunError Exception", ex);
- QGCActivity.nativeDeviceException(classPtr, ex.getMessage());
- }
-}
diff --git a/android/src/org/mavlink/qgroundcontrol/QGCUsbSerialManager.java b/android/src/org/mavlink/qgroundcontrol/QGCUsbSerialManager.java
new file mode 100644
index 00000000000..d8ef31e53ef
--- /dev/null
+++ b/android/src/org/mavlink/qgroundcontrol/QGCUsbSerialManager.java
@@ -0,0 +1,1159 @@
+package org.mavlink.qgroundcontrol;
+
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
+import android.content.*;
+import android.hardware.usb.*;
+import android.os.Process;
+import android.util.Log;
+
+import com.hoho.android.usbserial.driver.*;
+import com.hoho.android.usbserial.util.*;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.*;
+
+public class QGCUsbSerialManager {
+ private static final String TAG = QGCUsbSerialManager.class.getSimpleName();
+ private static final String ACTION_USB_PERMISSION = "org.mavlink.qgroundcontrol.action.USB_PERMISSION";
+ private static final int BAD_DEVICE_ID = 0;
+ private static final int READ_BUF_SIZE = 2048;
+
+ private static UsbManager usbManager;
+ private static PendingIntent usbPermissionIntent;
+ private static UsbSerialProber usbSerialProber;
+
+ private static List drivers = new CopyOnWriteArrayList<>();
+ private static ConcurrentHashMap deviceResourcesMap = new ConcurrentHashMap<>();
+
+ // Native methods
+ private static native void nativeDeviceHasDisconnected(final long classPtr);
+ public static native void nativeDeviceException(final long classPtr, final String message);
+ public static native void nativeDeviceNewData(final long classPtr, final byte[] data);
+ private static native void nativeUpdateAvailableJoysticks();
+
+ /**
+ * Encapsulates all resources associated with a USB device.
+ */
+ private static class UsbDeviceResources {
+ UsbSerialDriver driver;
+ SerialInputOutputManager ioManager;
+ int fileDescriptor;
+ long classPtr;
+
+ UsbDeviceResources(UsbSerialDriver driver) {
+ this.driver = driver;
+ }
+ }
+
+ /**
+ * Initializes the UsbSerialManager. Should be called once, typically from QGCActivity.onCreate().
+ *
+ * @param context The application context.
+ */
+ public static void initialize(Context context) {
+ if (usbManager != null) {
+ // Already initialized
+ return;
+ }
+
+ usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
+ setupUsbPermissionIntent(context);
+ registerUsbReceiver(context);
+ usbSerialProber = UsbSerialProber.getDefaultProber();
+ updateCurrentDrivers();
+ }
+
+ /**
+ * Cleans up resources by unregistering the BroadcastReceiver.
+ * Should be called when the manager is no longer needed, typically from QGCActivity.onDestroy().
+ */
+ public static void cleanup(Context context) {
+ try {
+ context.unregisterReceiver(usbReceiver);
+ QGCLogger.i(TAG, "BroadcastReceiver unregistered successfully.");
+ } catch (final IllegalArgumentException e) {
+ QGCLogger.w(TAG, "Receiver not registered: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Sets up the PendingIntent for USB permission requests.
+ *
+ * @param context The application context.
+ */
+ private static void setupUsbPermissionIntent(Context context) {
+ int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+ intentFlags = PendingIntent.FLAG_IMMUTABLE;
+ }
+ usbPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), intentFlags);
+ }
+
+ /**
+ * Registers the BroadcastReceiver to listen for USB-related events.
+ *
+ * @param context The application context.
+ */
+ private static void registerUsbReceiver(Context context) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ filter.addAction(ACTION_USB_PERMISSION);
+ // TODO: Move bluetooth handling back to QGCActivity
+ filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+ filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+
+ int flags = 0;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ flags = Context.RECEIVER_NOT_EXPORTED;
+ }
+
+ try {
+ context.registerReceiver(usbReceiver, filter, flags);
+ QGCLogger.i(TAG, "BroadcastReceiver registered successfully.");
+ } catch (Exception e) {
+ QGCLogger.e(TAG, "Failed to register BroadcastReceiver", e);
+ }
+ }
+
+ /**
+ * BroadcastReceiver to handle USB events.
+ */
+ private static final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ QGCLogger.i(TAG, "BroadcastReceiver USB action " + action);
+
+ switch (action) {
+ case ACTION_USB_PERMISSION:
+ handleUsbPermission(intent);
+ break;
+ case UsbManager.ACTION_USB_DEVICE_DETACHED:
+ handleUsbDeviceDetached(intent);
+ break;
+ case UsbManager.ACTION_USB_DEVICE_ATTACHED:
+ handleUsbDeviceAttached(intent);
+ break;
+ default:
+ break;
+ }
+
+ try {
+ nativeUpdateAvailableJoysticks();
+ } catch (final Exception ex) {
+ QGCLogger.e(TAG, "Exception nativeUpdateAvailableJoysticks()", ex);
+ }
+ }
+ };
+
+ /**
+ * Handles USB permission results.
+ *
+ * @param intent The intent containing permission data.
+ */
+ private static void handleUsbPermission(final Intent intent) {
+ UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (device != null) {
+ int deviceId = device.getDeviceId();
+ if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
+ QGCLogger.i(TAG, "Permission granted to " + device.getDeviceName());
+ addOrUpdateDevice(device);
+ } else {
+ QGCLogger.i(TAG, "Permission denied for " + device.getDeviceName());
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ if (resources != null) {
+ nativeDeviceException(resources.classPtr, "USB Permission Denied");
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles USB device detachment events.
+ *
+ * @param intent The intent containing device data.
+ */
+ private static void handleUsbDeviceDetached(final Intent intent) {
+ UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (device != null) {
+ int deviceId = device.getDeviceId();
+ UsbDeviceResources resources = deviceResourcesMap.remove(deviceId);
+ if (resources != null) {
+ nativeDeviceHasDisconnected(resources.classPtr);
+ }
+ QGCLogger.i(TAG, "Device detached: " + device.getDeviceName());
+ }
+ }
+
+ /**
+ * Handles USB device detachment events.
+ *
+ * @param intent The intent containing device data.
+ */
+ private static void handleUsbDeviceAttached(final Intent intent) {
+ UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (device != null) {
+ addOrUpdateDevice(device);
+ }
+ }
+
+ /**
+ * Adds a new device or updates an existing one.
+ *
+ * @param device The UsbDevice to add or update.
+ */
+ private static void addOrUpdateDevice(UsbDevice device) {
+ UsbSerialDriver driver = findDriverByDeviceId(device.getDeviceId());
+ if (driver != null) {
+ if (usbManager.hasPermission(device)) {
+ QGCLogger.i(TAG, "Already have permission to use device " + device.getDeviceName());
+ addDriver(driver);
+ } else {
+ QGCLogger.i(TAG, "Requesting permission to use device " + device.getDeviceName());
+ usbManager.requestPermission(device, usbPermissionIntent);
+ }
+ }
+ }
+
+ /**
+ * Checks if a device name is valid (i.e., exists in the current driver list).
+ *
+ * @param name The device name to check.
+ * @return True if valid, false otherwise.
+ */
+ public static boolean isDeviceNameValid(final String name) {
+ return drivers.stream().anyMatch(driver -> driver.getDevice().getDeviceName().equals(name));
+ }
+
+ /**
+ * Checks if a device name is currently open.
+ *
+ * @param name The device name to check.
+ * @return True if open, false otherwise.
+ */
+ public static boolean isDeviceNameOpen(final String name) {
+ int deviceId = getDeviceId(name);
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ return (port != null && port.isOpen());
+ }
+
+ /**
+ * Retrieves the device ID for a given device name.
+ *
+ * @param deviceName The device name.
+ * @return The device ID, or BAD_DEVICE_ID if not found.
+ */
+ public static int getDeviceId(final String deviceName) {
+ UsbSerialDriver driver = findDriverByDeviceName(deviceName);
+ if (driver == null) {
+ QGCLogger.w(TAG, "Attempt to get ID of unknown device " + deviceName);
+ return BAD_DEVICE_ID;
+ }
+
+ UsbDevice device = driver.getDevice();
+ return device.getDeviceId();
+ }
+
+ /**
+ * Retrieves the native device handle (file descriptor).
+ *
+ * @param deviceId The device ID.
+ * @return The device handle, or -1 if not found.
+ */
+ public static int getDeviceHandle(final int deviceId) {
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ return (resources != null) ? resources.fileDescriptor : -1;
+ }
+
+ /**
+ * Updates the current list of USB serial drivers by scanning connected devices.
+ *
+ * @param deviceId The device ID to update, or -1 to update all.
+ */
+ private static void updateCurrentDrivers() {
+ final List currentDrivers = usbSerialProber.findAllDrivers(usbManager);
+ if (currentDrivers.isEmpty()) {
+ return;
+ }
+
+ removeStaleDrivers(currentDrivers);
+ addNewDrivers(currentDrivers);
+ }
+
+ /**
+ * Removes drivers that are no longer connected.
+ *
+ * @param currentDrivers The list of currently connected drivers.
+ */
+ private static void removeStaleDrivers(final List currentDrivers) {
+ for (int i = drivers.size() - 1; i >= 0; i--) {
+ UsbSerialDriver existingDriver = drivers.get(i);
+ boolean found = currentDrivers.stream()
+ .anyMatch(currentDriver -> currentDriver.getDevice().getDeviceId() == existingDriver.getDevice().getDeviceId());
+
+ if (!found) {
+ int deviceId = existingDriver.getDevice().getDeviceId();
+ deviceResourcesMap.remove(deviceId);
+ drivers.remove(i);
+ QGCLogger.i(TAG, "Removed stale driver for device ID " + deviceId);
+ }
+ }
+ }
+
+ /**
+ * Adds new drivers that are not already in the driver list.
+ *
+ * @param currentDrivers The list of currently connected drivers.
+ */
+ private static void addNewDrivers(final List currentDrivers) {
+ for (UsbSerialDriver newDriver : currentDrivers) {
+ boolean found = drivers.stream()
+ .anyMatch(existingDriver -> existingDriver.getDevice().getDeviceId() == newDriver.getDevice().getDeviceId());
+
+ if (!found) {
+ addDriver(newDriver);
+ }
+ }
+ }
+
+ /**
+ * Adds a new USB serial driver to the driver list and requests permission if needed.
+ *
+ * @param newDriver The UsbSerialDriver to add.
+ */
+ private static void addDriver(final UsbSerialDriver newDriver) {
+ UsbDevice device = newDriver.getDevice();
+ String deviceName = device.getDeviceName();
+
+ drivers.add(newDriver);
+ deviceResourcesMap.put(device.getDeviceId(), new UsbDeviceResources(newDriver));
+ QGCLogger.i(TAG, "Adding new driver for device ID " + device.getDeviceId() + ": " + deviceName);
+
+ if (usbManager.hasPermission(device)) {
+ QGCLogger.i(TAG, "Already have permission to use device " + deviceName);
+ } else {
+ QGCLogger.i(TAG, "Requesting permission to use device " + deviceName);
+ usbManager.requestPermission(device, usbPermissionIntent);
+ }
+ }
+
+ /**
+ * Finds a USB serial driver by its device ID.
+ *
+ * @param deviceId The device ID.
+ * @return The corresponding UsbSerialDriver or null if not found.
+ */
+ private static UsbSerialDriver findDriverByDeviceId(final int deviceId) {
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ return (resources != null) ? resources.driver : null;
+ }
+
+ /**
+ * Finds a USB serial driver by its device name.
+ *
+ * @param deviceName The device name.
+ * @return The corresponding UsbSerialDriver or null if not found.
+ */
+ private static UsbSerialDriver findDriverByDeviceName(final String deviceName) {
+ for (UsbDeviceResources resources : deviceResourcesMap.values()) {
+ if (resources.driver.getDevice().getDeviceName().equals(deviceName)) {
+ return resources.driver;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds a USB serial port by its device ID.
+ *
+ * @param deviceId The device ID.
+ * @return The corresponding UsbSerialPort or null if not found.
+ */
+ private static UsbSerialPort findPortByDeviceId(final int deviceId) {
+ final int portIndex = 0;
+
+ if (deviceId == BAD_DEVICE_ID) {
+ QGCLogger.w(TAG, "Finding port failed for invalid Device ID " + deviceId);
+ return null;
+ }
+
+ UsbSerialDriver driver = findDriverByDeviceId(deviceId);
+ if (driver == null) {
+ QGCLogger.w(TAG, "No driver found on device ID " + deviceId);
+ return null;
+ }
+
+ List ports = driver.getPorts();
+ if (ports.isEmpty()) {
+ QGCLogger.w(TAG, "No ports available on device ID " + deviceId);
+ return null;
+ }
+
+ if (portIndex < 0 || portIndex >= ports.size()) {
+ QGCLogger.w(TAG, "Invalid port index " + portIndex + " for device ID " + deviceId);
+ return null;
+ }
+
+ return ports.get(portIndex);
+ }
+
+ /**
+ * Retrieves information about all available USB serial devices.
+ *
+ * @return An array of device information strings or null if no devices are available.
+ */
+ public static String[] availableDevicesInfo() {
+ updateCurrentDrivers();
+
+ if (usbManager.getDeviceList().size() < 1) {
+ return null;
+ }
+
+ final List deviceInfoList = new ArrayList<>();
+
+ for (final UsbDevice device : usbManager.getDeviceList().values()) {
+ final String deviceInfo = formatDeviceInfo(device);
+ deviceInfoList.add(deviceInfo);
+ }
+
+ return deviceInfoList.toArray(new String[0]);
+ }
+
+ /**
+ * Formats device information into a standardized string.
+ *
+ * @param device The UsbDevice to format.
+ * @return A formatted string containing device information.
+ */
+ private static String formatDeviceInfo(final UsbDevice device) {
+ StringBuilder deviceInfo = new StringBuilder();
+ deviceInfo.append(device.getDeviceName()).append(":")
+ .append(device.getProductName()).append(":")
+ .append(device.getManufacturerName()).append(":")
+ .append(device.getSerialNumber()).append(":")
+ .append(device.getProductId()).append(":")
+ .append(device.getVendorId());
+
+ QGCLogger.i(TAG, "Formatted Device Info: " + deviceInfo.toString());
+
+ return deviceInfo.toString();
+ }
+
+ /**
+ * Opens a USB serial device.
+ *
+ * @param deviceName The name of the device to open.
+ * @param classPtr A native pointer associated with the device.
+ * @return The device ID if successful, or BAD_DEVICE_ID if failed.
+ */
+ public static int open(final String deviceName, final long classPtr) {
+ UsbSerialDriver driver = findDriverByDeviceName(deviceName);
+ if (driver == null) {
+ QGCLogger.w(TAG, "Attempt to open unknown device " + deviceName);
+ return BAD_DEVICE_ID;
+ }
+
+ List ports = driver.getPorts();
+ if (ports.isEmpty()) {
+ QGCLogger.w(TAG, "No ports available on device " + deviceName);
+ return BAD_DEVICE_ID;
+ }
+
+ UsbDevice device = driver.getDevice();
+ int deviceId = device.getDeviceId();
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+
+ if (!openDriver(port, device, deviceId, classPtr)) {
+ QGCLogger.e(TAG, "Failed to open driver for device " + deviceName);
+ nativeDeviceException(classPtr, "Failed to open driver for device: " + deviceName);
+ return BAD_DEVICE_ID;
+ }
+
+ if (!createIoManager(deviceId, port, classPtr)) {
+ return BAD_DEVICE_ID;
+ }
+
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ if (resources != null) {
+ resources.classPtr = classPtr;
+ }
+
+ QGCLogger.d(TAG, "Port open successful: " + port.toString());
+ return deviceId;
+ }
+
+ /**
+ * Opens the driver for a specific USB serial port.
+ *
+ * @param port The UsbSerialPort to open.
+ * @param device The UsbDevice associated with the port.
+ * @param deviceId The device ID.
+ * @param classPtr A native pointer associated with the device.
+ * @return True if successful, false otherwise.
+ */
+ private static boolean openDriver(final UsbSerialPort port, final UsbDevice device, final int deviceId, final long classPtr) {
+ UsbDeviceConnection connection = usbManager.openDevice(device);
+ if (connection == null) {
+ QGCLogger.w(TAG, "No Usb Device Connection");
+ nativeDeviceException(classPtr, "No USB device connection for device: " + device.getDeviceName());
+ return false;
+ }
+
+ try {
+ port.open(connection);
+ } catch (final IOException ex) {
+ QGCLogger.e(TAG, "Error opening driver for device " + device.getDeviceName(), ex);
+ nativeDeviceException(classPtr, "Error opening driver: " + ex.getMessage());
+ return false;
+ }
+
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ if (resources != null) {
+ resources.fileDescriptor = connection.getFileDescriptor();
+ }
+
+ if (!setParameters(deviceId, 9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)) {
+ return false;
+ }
+
+ QGCLogger.d(TAG, "Port Driver open successful");
+ return true;
+ }
+
+ /**
+ * Creates and initializes the SerialInputOutputManager for a device.
+ *
+ * @param deviceId The device ID.
+ * @param port The UsbSerialPort to manage.
+ * @param classPtr A native pointer associated with the device.
+ * @return True if successful, false otherwise.
+ */
+ private static boolean createIoManager(final int deviceId, final UsbSerialPort port, final long classPtr) {
+ if (deviceResourcesMap.get(deviceId) == null) {
+ QGCLogger.w(TAG, "No resources found for device ID " + deviceId);
+ return false;
+ }
+
+ if (deviceResourcesMap.get(deviceId).ioManager != null) {
+ QGCLogger.i(TAG, "IO Manager already exists for device ID " + deviceId);
+ return true;
+ }
+
+ QGCSerialListener listener = new QGCSerialListener(classPtr);
+ SerialInputOutputManager ioManager = new SerialInputOutputManager(port, listener);
+
+ ioManager.setReadBufferSize(Math.max(port.getReadEndpoint().getMaxPacketSize(), READ_BUF_SIZE));
+
+ QGCLogger.d(TAG, "Read Buffer Size: " + ioManager.getReadBufferSize());
+ QGCLogger.d(TAG, "Write Buffer Size: " + ioManager.getWriteBufferSize());
+
+ try {
+ ioManager.setReadTimeout(0);
+ ioManager.setWriteTimeout(0);
+ ioManager.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
+ } catch (final IllegalStateException e) {
+ QGCLogger.e(TAG, "IO Manager configuration error:", e);
+ return false;
+ }
+
+ deviceResourcesMap.get(deviceId).ioManager = ioManager;
+ QGCLogger.d(TAG, "Serial I/O Manager created for device ID " + deviceId);
+ return true;
+ }
+
+ /**
+ * Starts the SerialInputOutputManager for a specific device.
+ *
+ * @param deviceId The device ID.
+ * @return True if successful or already running, false otherwise.
+ */
+ public static boolean startIoManager(final int deviceId) {
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ if (resources == null || resources.ioManager == null) {
+ QGCLogger.w(TAG, "IO Manager not found for device ID " + deviceId);
+ return false;
+ }
+
+ SerialInputOutputManager.State ioState = resources.ioManager.getState();
+ if (ioState == SerialInputOutputManager.State.RUNNING) {
+ return true;
+ }
+
+ try {
+ resources.ioManager.start();
+ QGCLogger.d(TAG, "Serial I/O Manager started for device ID " + deviceId);
+ return true;
+ } catch (final IllegalStateException e) {
+ QGCLogger.e(TAG, "IO Manager Start exception:", e);
+ return false;
+ }
+ }
+
+ /**
+ * Stops the SerialInputOutputManager for a specific device.
+ *
+ * @param deviceId The device ID.
+ * @return True if successful or already stopped, false otherwise.
+ */
+ public static boolean stopIoManager(final int deviceId) {
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ if (resources == null || resources.ioManager == null) {
+ return false;
+ }
+
+ SerialInputOutputManager.State ioState = resources.ioManager.getState();
+ if (ioState == SerialInputOutputManager.State.STOPPED || ioState == SerialInputOutputManager.State.STOPPING) {
+ return true;
+ }
+
+ resources.ioManager.stop();
+ QGCLogger.d(TAG, "Serial I/O Manager stopped for device ID " + deviceId);
+ return true;
+ }
+
+ /**
+ * Checks if the SerialInputOutputManager is running for a specific device.
+ *
+ * @param deviceId The device ID.
+ * @return True if running, false otherwise.
+ */
+ public static boolean ioManagerRunning(final int deviceId) {
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ if (resources == null || resources.ioManager == null) {
+ return false;
+ }
+
+ SerialInputOutputManager.State ioState = resources.ioManager.getState();
+ return (ioState == SerialInputOutputManager.State.RUNNING);
+ }
+
+ /**
+ * Closes the USB serial device.
+ *
+ * @param deviceId The device ID.
+ * @return True if successful, false otherwise.
+ */
+ public static boolean close(int deviceId) {
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ if (resources == null) {
+ QGCLogger.w(TAG, "Attempted to close a non-existent device ID " + deviceId);
+ return false;
+ }
+
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null) {
+ QGCLogger.w(TAG, "Attempted to close a null port for device ID " + deviceId);
+ return false;
+ }
+
+ if (!port.isOpen()) {
+ QGCLogger.w(TAG, "Attempted to close an already closed port for device ID " + deviceId);
+ return false;
+ }
+
+ stopIoManager(deviceId);
+ deviceResourcesMap.remove(deviceId);
+
+ try {
+ port.close();
+ QGCLogger.d(TAG, "Device " + deviceId + " closed successfully.");
+ return true;
+ } catch (final IOException ex) {
+ QGCLogger.e(TAG, "Error closing driver:", ex);
+ return false;
+ }
+ }
+
+ /**
+ * Writes data to the USB serial device.
+ *
+ * @param deviceId The device ID.
+ * @param data The byte array of data to write.
+ * @param length The number of bytes to write.
+ * @param timeoutMSec The timeout in milliseconds.
+ * @return The number of bytes written, or -1 if failed.
+ */
+ public static int write(final int deviceId, final byte[] data, final int length, final int timeoutMSec) {
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null) {
+ QGCLogger.w(TAG, "Attempted to write to a null port for device ID " + deviceId);
+ return -1;
+ }
+
+ if (!port.isOpen()) {
+ QGCLogger.w(TAG, "Attempted to write to a closed port for device ID " + deviceId);
+ return -1;
+ }
+
+ try {
+ port.write(data, length, timeoutMSec);
+ return length;
+ } catch (final SerialTimeoutException e) {
+ QGCLogger.e(TAG, "Write timeout occurred", e);
+ return -1;
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error writing data", e);
+ return -1;
+ }
+ }
+
+ /**
+ * Writes data asynchronously to the USB serial device.
+ *
+ * @param deviceId The device ID.
+ * @param data The byte array of data to write.
+ * @param timeoutMSec The timeout in milliseconds.
+ * @return The number of bytes written, or -1 if failed.
+ */
+ public static int writeAsync(final int deviceId, final byte[] data, final int timeoutMSec) {
+ UsbDeviceResources resources = deviceResourcesMap.get(deviceId);
+ if (resources == null || resources.ioManager == null) {
+ QGCLogger.w(TAG, "IO Manager not found for device ID " + deviceId);
+ return -1;
+ }
+
+ if (resources.ioManager.getReadTimeout() == 0) {
+ QGCLogger.w(TAG, "Read Timeout is 0 for writeAsync");
+ }
+
+ resources.ioManager.setWriteTimeout(timeoutMSec);
+ resources.ioManager.writeAsync(data);
+
+ return data.length;
+ }
+
+ /**
+ * Reads data from the USB serial device.
+ *
+ * @param deviceId The device ID.
+ * @param length The number of bytes to read.
+ * @param timeoutMs The timeout in milliseconds.
+ * @return A byte array containing the read data.
+ */
+ public static byte[] read(final int deviceId, final int length, final int timeoutMs) {
+ if (timeoutMs < 500) {
+ QGCLogger.w(TAG, "Read with timeout less than recommended minimum of 200-500ms");
+ }
+
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null) {
+ QGCLogger.w(TAG, "Attempted to read from a null port for device ID " + deviceId);
+ return new byte[]{};
+ }
+
+ if (!port.isOpen()) {
+ QGCLogger.w(TAG, "Attempted to read from a closed port for device ID " + deviceId);
+ return new byte[]{};
+ }
+
+ byte[] buffer = new byte[length];
+ int bytesRead = 0;
+
+ try {
+ bytesRead = port.read(buffer, timeoutMs);
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error reading data", e);
+ }
+
+ if (bytesRead < length) {
+ return Arrays.copyOf(buffer, bytesRead);
+ }
+
+ return buffer;
+ }
+
+ /**
+ * Sets the parameters on an open USB serial port.
+ *
+ * @param deviceId The device ID.
+ * @param baudRate The baud rate (e.g., 9600, 115200).
+ * @param dataBits The number of data bits (5, 6, 7, 8).
+ * @param stopBits The number of stop bits (1, 2).
+ * @param parity The parity setting (0: None, 1: Odd, 2: Even).
+ * @return True if successful, false otherwise.
+ */
+ public static boolean setParameters(final int deviceId, final int baudRate, final int dataBits, final int stopBits, final int parity) {
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null) {
+ QGCLogger.w(TAG, "Attempted to set parameters to a null port for device ID " + deviceId);
+ return false;
+ }
+
+ if (!port.isOpen()) {
+ QGCLogger.w(TAG, "Attempted to set parameters on a closed port for device ID " + deviceId);
+ return false;
+ }
+
+ try {
+ port.setParameters(baudRate, dataBits, stopBits, parity);
+ } catch (final UnsupportedOperationException e) {
+ QGCLogger.w(TAG, "Error setting parameters" + ": " + e);
+ return false;
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error setting parameters", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ private static boolean getControlLine(int deviceId, UsbSerialPort.ControlLine controlLine) {
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null) {
+ QGCLogger.w(TAG, "Attempted to get " + controlLine + " from a null port for device ID " + deviceId);
+ return false;
+ }
+
+ if (!isControlLineSupported(port, controlLine)) {
+ QGCLogger.w(TAG, "Getting " + controlLine + " Not Supported");
+ return false;
+ }
+
+ try {
+ switch (controlLine) {
+ case CD:
+ return port.getCD();
+ case CTS:
+ return port.getCTS();
+ case DSR:
+ return port.getDSR();
+ case DTR:
+ return port.getDTR();
+ case RI:
+ return port.getRI();
+ case RTS:
+ return port.getRTS();
+ default:
+ return false;
+ }
+ } catch (final UnsupportedOperationException e) {
+ QGCLogger.w(TAG, "Error getting " + controlLine + ": " + e);
+ return false;
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error getting " + controlLine, e);
+ return false;
+ }
+ }
+
+ private static boolean setControlLine(int deviceId, UsbSerialPort.ControlLine controlLine, boolean on) {
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null) {
+ QGCLogger.w(TAG, "Attempted to set " + controlLine + " on a null port for device ID " + deviceId);
+ return false;
+ }
+
+ if (!isControlLineSupported(port, controlLine)) {
+ QGCLogger.e(TAG, "Setting " + controlLine + " Not Supported");
+ return false;
+ }
+
+ try {
+ switch (controlLine) {
+ case DTR:
+ port.setDTR(on);
+ break;
+ case RTS:
+ port.setRTS(on);
+ break;
+ default:
+ QGCLogger.w(TAG, "Setting " + controlLine + " is not supported via this method.");
+ return false;
+ }
+ } catch (final UnsupportedOperationException e) {
+ QGCLogger.w(TAG, "Error setting " + controlLine + ": " + e);
+ return false;
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error setting " + controlLine, e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if a specific control line is supported by the device.
+ *
+ * @param port The UsbSerialPort.
+ * @param controlLine The control line to check.
+ * @return True if supported, false otherwise.
+ */
+ private static boolean isControlLineSupported(final UsbSerialPort port, final UsbSerialPort.ControlLine controlLine) {
+ EnumSet supportedControlLines;
+
+ try {
+ supportedControlLines = port.getSupportedControlLines();
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error getting supported control lines", e);
+ return false;
+ }
+
+ return supportedControlLines.contains(controlLine);
+ }
+
+ /**
+ * Retrieves the carrier detect (CD) flag from the device.
+ *
+ * @param deviceId The device ID.
+ * @return True if CD is active, false otherwise.
+ */
+ public static boolean getCarrierDetect(final int deviceId) {
+ return getControlLine(deviceId, UsbSerialPort.ControlLine.CD);
+ }
+
+ /**
+ * Retrieves the clear to send (CTS) flag from the device.
+ *
+ * @param deviceId The device ID.
+ * @return True if CTS is active, false otherwise.
+ */
+ public static boolean getClearToSend(final int deviceId) {
+ return getControlLine(deviceId, UsbSerialPort.ControlLine.CTS);
+ }
+
+ /**
+ * Retrieves the data set ready (DSR) flag from the device.
+ *
+ * @param deviceId The device ID.
+ * @return True if DSR is active, false otherwise.
+ */
+ public static boolean getDataSetReady(final int deviceId) {
+ return getControlLine(deviceId, UsbSerialPort.ControlLine.DSR);
+ }
+
+ /**
+ * Retrieves the data terminal ready (DTR) flag from the device.
+ *
+ * @param deviceId The device ID.
+ * @return True if DTR is active, false otherwise.
+ */
+ public static boolean getDataTerminalReady(final int deviceId) {
+ return getControlLine(deviceId, UsbSerialPort.ControlLine.DTR);
+ }
+
+ /**
+ * Sets the data terminal ready (DTR) flag on the device.
+ *
+ * @param deviceId The device ID.
+ * @param on True to set DTR, false to clear.
+ * @return True if successful, false otherwise.
+ */
+ public static boolean setDataTerminalReady(final int deviceId, final boolean on) {
+ return setControlLine(deviceId, UsbSerialPort.ControlLine.DTR, on);
+ }
+
+ /**
+ * Retrieves the request to send (RTS) flag from the device.
+ *
+ * @param deviceId The device ID.
+ * @return True if RTS is active, false otherwise.
+ */
+ public static boolean getRequestToSend(final int deviceId) {
+ return getControlLine(deviceId, UsbSerialPort.ControlLine.RTS);
+ }
+
+ /**
+ * Sets the request to send (RTS) flag on the device.
+ *
+ * @param deviceId The device ID.
+ * @param on True to set RTS, false to clear.
+ * @return True if successful, false otherwise.
+ */
+ public static boolean setRequestToSend(final int deviceId, final boolean on) {
+ return setControlLine(deviceId, UsbSerialPort.ControlLine.RTS, on);
+ }
+
+ /**
+ * Retrieves the ring indicator (RI) flag from the device.
+ *
+ * @param deviceId The device ID.
+ * @return True if RI is active, false otherwise.
+ */
+ public static boolean getRingIndicator(final int deviceId) {
+ return getControlLine(deviceId, UsbSerialPort.ControlLine.RI);
+ }
+
+ /**
+ * Retrieves the supported control lines from the device.
+ *
+ * @param deviceId The device ID.
+ * @return An array of control line ordinals.
+ */
+ public static int[] getControlLines(final int deviceId) {
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null || !port.isOpen()) {
+ QGCLogger.w(TAG, "Attempted to get control lines from a null or closed port for device ID " + deviceId);
+ return new int[]{};
+ }
+
+ EnumSet currentControlLines;
+
+ try {
+ currentControlLines = port.getControlLines();
+ } catch (final UnsupportedOperationException e) {
+ QGCLogger.w(TAG, "Error getting control lines: " + e);
+ return new int[]{};
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error getting control lines", e);
+ return new int[]{};
+ }
+
+ int[] lines = currentControlLines.stream().mapToInt(UsbSerialPort.ControlLine::ordinal).toArray();
+ return lines;
+ }
+
+ /**
+ * Retrieves the current flow control setting from the device.
+ *
+ * @param deviceId The device ID.
+ * @return The flow control ordinal, or 0 if not supported.
+ */
+ public static int getFlowControl(final int deviceId) {
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null || !port.isOpen()) {
+ QGCLogger.w(TAG, "Attempted to get flow control from a null or closed port for device ID " + deviceId);
+ return 0;
+ }
+
+ EnumSet supportedFlowControl = port.getSupportedFlowControl();
+ if (supportedFlowControl.isEmpty()) {
+ QGCLogger.e(TAG, "Flow Control Not Supported");
+ return 0;
+ }
+
+ UsbSerialPort.FlowControl flowControl = port.getFlowControl();
+ return flowControl.ordinal();
+ }
+
+ /**
+ * Sets the flow control setting on the device.
+ *
+ * @param deviceId The device ID.
+ * @param flowControl The flow control ordinal.
+ * @return True if successful, false otherwise.
+ */
+ public static boolean setFlowControl(final int deviceId, final int flowControl) {
+ if (getFlowControl(deviceId) == flowControl) {
+ return true;
+ }
+
+ if (flowControl < 0 || flowControl >= UsbSerialPort.FlowControl.values().length) {
+ QGCLogger.w(TAG, "Invalid flow control ordinal " + flowControl);
+ return false;
+ }
+
+ UsbSerialPort.FlowControl flowControlEnum = UsbSerialPort.FlowControl.values()[flowControl];
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+
+ if (port == null || !port.isOpen()) {
+ QGCLogger.w(TAG, "Attempted to set flow control on a null or closed port for device ID " + deviceId);
+ return false;
+ }
+
+ EnumSet supportedFlowControl = port.getSupportedFlowControl();
+ if (!supportedFlowControl.contains(flowControlEnum)) {
+ QGCLogger.e(TAG, "Setting Flow Control Not Supported");
+ return false;
+ }
+
+ try {
+ port.setFlowControl(flowControlEnum);
+ } catch (final UnsupportedOperationException e) {
+ QGCLogger.w(TAG, "Error setting Flow Control: " + e);
+ return false;
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error setting Flow Control", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets the break condition on the device.
+ *
+ * @param deviceId The device ID.
+ * @param on True to set break, false to clear break.
+ * @return True if successful, false otherwise.
+ */
+ public static boolean setBreak(final int deviceId, final boolean on) {
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null || !port.isOpen()) {
+ QGCLogger.w(TAG, "Attempted to set break on a null or closed port for device ID " + deviceId);
+ return false;
+ }
+
+ try {
+ port.setBreak(on);
+ } catch (final UnsupportedOperationException e) {
+ QGCLogger.w(TAG, "Error setting break condition: " + e);
+ return false;
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error setting break condition", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Purges the hardware buffers on the device.
+ *
+ * @param deviceId The device ID.
+ * @param input True to purge the input buffer.
+ * @param output True to purge the output buffer.
+ * @return True if successful, false otherwise.
+ */
+ public static boolean purgeBuffers(final int deviceId, final boolean input, final boolean output) {
+ UsbSerialPort port = findPortByDeviceId(deviceId);
+ if (port == null || !port.isOpen()) {
+ QGCLogger.w(TAG, "Attempted to purge buffers on a null or closed port for device ID " + deviceId);
+ return false;
+ }
+
+ try {
+ port.purgeHwBuffers(input, output);
+ } catch (final UnsupportedOperationException e) {
+ QGCLogger.w(TAG, "Error purging buffers: " + e);
+ return false;
+ } catch (final IOException e) {
+ QGCLogger.e(TAG, "Error purging buffers", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Inner class to handle serial data callbacks.
+ */
+ private static class QGCSerialListener implements SerialInputOutputManager.Listener {
+ private long classPtr;
+
+ public QGCSerialListener(long classPtr) {
+ this.classPtr = classPtr;
+ }
+
+ @Override
+ public void onRunError(Exception e) {
+ QGCLogger.e(TAG, "Runner stopped.", e);
+ nativeDeviceException(classPtr, "Runner stopped: " + e.getMessage());
+ }
+
+ @Override
+ public void onNewData(final byte[] data) {
+ if (isValidData(data)) {
+ nativeDeviceNewData(classPtr, data);
+ } else {
+ QGCLogger.w(TAG, "Invalid data received: " + Arrays.toString(data));
+ }
+ }
+
+ private boolean isValidData(byte[] data) {
+ return ((data != null) && (data.length > 0));
+ }
+ }
+}
diff --git a/src/Comms/SerialLink.cc b/src/Comms/SerialLink.cc
index eecad92fbd9..f293945c511 100644
--- a/src/Comms/SerialLink.cc
+++ b/src/Comms/SerialLink.cc
@@ -253,7 +253,7 @@ void SerialLink::linkError(QSerialPort::SerialPortError error)
// when you are done. The reason for this is that this signal is very noisy. For example if you try to
// connect to a PixHawk before it is ready to accept the connection it will output a continuous stream
// of errors until the Pixhawk responds.
- //qCDebug(SerialLinkLog) << "SerialLink::linkError" << error;
+ // qCDebug(SerialLinkLog) << "SerialLink::linkError" << error;
break;
}
}
diff --git a/src/Joystick/JoystickAndroid.cc b/src/Joystick/JoystickAndroid.cc
index f574be03bf8..0bbf431ea7c 100644
--- a/src/Joystick/JoystickAndroid.cc
+++ b/src/Joystick/JoystickAndroid.cc
@@ -285,7 +285,7 @@ bool JoystickAndroid::init() {
return true;
}
-static const char kJniClassName[] {"org/mavlink/qgroundcontrol/QGCActivity"};
+static const char kJniClassName[] {"org/mavlink/qgroundcontrol/QGCUsbSerialManager"};
static void jniUpdateAvailableJoysticks(JNIEnv *envA, jobject thizA)
{