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) {