5#include <QtCore/QJniEnvironment>
6#include <QtCore/QJniObject>
7#include <QtCore/QMutex>
8#include <QtCore/QPointer>
9#include <QtCore/QRandomGenerator>
10#include <QtCore/QReadWriteLock>
11#include <QtCore/QThread>
36 qCDebug(AndroidSerialLog) <<
"Serial backend:" << (use ?
"POSIX" :
"Java USB");
47 static const QStringList kUartPatterns = {
48 QStringLiteral(
"ttyS*"), QStringLiteral(
"ttyHS*"), QStringLiteral(
"ttyMSM*"),
49 QStringLiteral(
"ttyHSL*"), QStringLiteral(
"ttymxc*"), QStringLiteral(
"ttyAMA*"),
50 QStringLiteral(
"ttyTHS*"),
53 QList<QSerialPortInfo> serialPortInfoList;
55 const QStringList deviceNames =
56 QDir(QStringLiteral(
"/dev")).entryList(kUartPatterns, QDir::System | QDir::Files, QDir::Name);
57 for (
const QString& deviceName : deviceNames) {
58 const QString systemLocation = QStringLiteral(
"/dev/") + deviceName;
59 if (::access(systemLocation.toLocal8Bit().constData(), R_OK | W_OK) != 0) {
65 info.
device = systemLocation;
66 serialPortInfoList.append(info);
69 return serialPortInfoList;
94 qCWarning(AndroidSerialLog) <<
"registerPointer called with null pointer";
108 token =
static_cast<jlong
>(QRandomGenerator::global()->generate64());
144 if (!serialPortPrivate) {
148 return qobject_cast<QSerialPort*>(serialPortPrivate->q_ptr);
151template <
typename Functor>
155 qCWarning(AndroidSerialLog) << context <<
": null serial port";
159 QThread*
const targetThread = serialPort->thread();
160 const bool sameThread = (targetThread == QThread::currentThread());
161 const bool hasEventLoop = targetThread && targetThread->eventDispatcher();
164 std::forward<Functor>(func)();
172 const bool ok = QMetaObject::invokeMethod(serialPort, std::forward<Functor>(func), Qt::BlockingQueuedConnection);
174 qCWarning(AndroidSerialLog) << context <<
": failed to invoke method on target thread";
179 qCWarning(AndroidSerialLog) << context <<
": target thread has no event loop, running inline fallback";
180 std::forward<Functor>(func)();
232 const MethodDef defs[] = {
261 for (
const auto& def : defs) {
262 *def.target = env->GetStaticMethodID(javaClass, def.name, def.sig);
264 qCWarning(AndroidSerialLog) <<
"Failed to cache method:" << def.name << def.sig;
265 (void)QJniEnvironment::checkAndClearExceptions(env);
271 qCDebug(AndroidSerialLog) <<
"All JNI method IDs cached successfully";
288 if (!env.isValid()) {
289 qCWarning(AndroidSerialLog) <<
"Invalid QJniEnvironment";
295 if (!resolvedClass) {
301 if (env->GetObjectRefType(resolvedClass) == JNILocalRefType) {
302 env->DeleteLocalRef(resolvedClass);
307 (void)env.checkAndClearExceptions();
313 qCWarning(AndroidSerialLog) <<
"Failed to cache JNI method IDs";
317 (void)env.checkAndClearExceptions();
322 (void)env.checkAndClearExceptions();
344static void jniDeviceNewData(JNIEnv* env, jobject obj, jlong token, jbyteArray data);
345static void jniDeviceException(JNIEnv* env, jobject obj, jlong token, jstring message);
349 qCDebug(AndroidSerialLog) <<
"Registering Native Functions";
351 const JNINativeMethod javaMethods[]{
353 {
"nativeDeviceNewData",
"(J[B)V",
reinterpret_cast<void*
>(
jniDeviceNewData)},
354 {
"nativeDeviceException",
"(JLjava/lang/String;)V",
reinterpret_cast<void*
>(
jniDeviceException)},
364 qCWarning(AndroidSerialLog) <<
"Failed to cache JNI method IDs";
368 qCDebug(AndroidSerialLog) <<
"Native Functions Registered Successfully";
378 qCWarning(AndroidSerialLog) <<
"nativeDeviceHasDisconnected called with token=0";
382 QPointer<QSerialPort> serialPort;
387 qCWarning(AndroidSerialLog) <<
"nativeDeviceHasDisconnected: stale token, object already destroyed";
390 qCDebug(AndroidSerialLog) <<
"Device disconnected:" << serialPort->portName();
396 QSerialPortPrivate* const p = lookupByToken(token);
398 qCDebug(AndroidSerialLog) <<
"Token already invalidated in nativeDeviceHasDisconnected";
402 QSerialPort*
const port = qobject_cast<QSerialPort*>(p->q_ptr);
403 if (port && port->isOpen()) {
405 qCDebug(AndroidSerialLog) <<
"Serial port closed in nativeDeviceHasDisconnected";
407 qCDebug(AndroidSerialLog) <<
"Serial port was already closed in nativeDeviceHasDisconnected";
410 "nativeDeviceHasDisconnected")) {
411 qCWarning(AndroidSerialLog) <<
"nativeDeviceHasDisconnected: failed to dispatch cleanup";
417 constexpr jsize kMaxNativePayloadBytes =
static_cast<jsize
>(
MAX_READ_SIZE);
420 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData called with token=0";
425 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData called with null data";
429 const jsize len = env->GetArrayLength(data);
431 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData received empty data array";
435 const jsize cappedLen = (len > kMaxNativePayloadBytes) ? kMaxNativePayloadBytes : len;
436 if (cappedLen != len) {
437 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData payload exceeds limit, truncating from" << len <<
"to"
438 << cappedLen <<
"bytes";
441 QByteArray payload(cappedLen, Qt::Uninitialized);
442 env->GetByteArrayRegion(data, 0, cappedLen,
reinterpret_cast<jbyte*
>(payload.data()));
443 if (QJniEnvironment::checkAndClearExceptions(env)) {
444 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData failed to copy JNI byte array";
453 if (!serialPortPrivate) {
454 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData: stale token, object already destroyed";
458 serialPortPrivate->
newDataArrived(payload.constData(), payload.size());
465 qCWarning(AndroidSerialLog) <<
"nativeDeviceException called with token=0";
470 qCWarning(AndroidSerialLog) <<
"nativeDeviceException called with null message";
474 const QString exceptionMessage = QJniObject(message).toString();
476 QPointer<QSerialPort> serialPort;
481 qCWarning(AndroidSerialLog) <<
"nativeDeviceException: stale token, object already destroyed";
486 qCWarning(AndroidSerialLog) <<
"Exception from Java:" << exceptionMessage;
490 [token, exceptionMessage]() {
491 QSerialPortPrivate* const p = lookupByToken(token);
493 qCDebug(AndroidSerialLog) <<
"Token already invalidated in nativeDeviceException";
497 p->exceptionArrived(exceptionMessage);
499 "nativeDeviceException")) {
500 qCWarning(AndroidSerialLog) <<
"nativeDeviceException: failed to dispatch exception callback";
511 jclass cls =
nullptr;
517 if (!ctx.
env.isValid()) {
518 qCWarning(AndroidSerialLog) <<
"Invalid QJniEnvironment in" << caller;
524 qCWarning(AndroidSerialLog) <<
"getSerialManagerClass returned null in" << caller;
538 QList<QSerialPortInfo> serialPortInfoList;
542 return serialPortInfoList;
547 if (!objArray.
get()) {
548 qCDebug(AndroidSerialLog) <<
"availableDevicesInfo returned null";
549 (void)ctx.
env.checkAndClearExceptions();
550 return serialPortInfoList;
553 if (ctx.
env.checkAndClearExceptions()) {
554 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling availableDevicesInfo";
555 return serialPortInfoList;
558 const jsize count = ctx.
env->GetArrayLength(objArray.
get());
559 for (jsize i = 0; i < count; ++i) {
561 ctx.
env.jniEnv(),
static_cast<jstring
>(ctx.
env->GetObjectArrayElement(objArray.
get(), i)));
563 qCWarning(AndroidSerialLog) <<
"Null string at index" << i;
567 const QStringList strList = QJniObject(jstr.
get()).toString().split(QLatin1Char(
'\t'));
569 if (strList.size() < 6) {
570 qCWarning(AndroidSerialLog) <<
"Invalid device info at index" << i <<
":" << strList;
586 serialPortInfoList.append(info);
589 (void)ctx.
env.checkAndClearExceptions();
591 return serialPortInfoList;
600 const QJniObject name = QJniObject::fromString(portName);
601 if (!name.isValid()) {
602 qCWarning(AndroidSerialLog) <<
"Invalid QJniObject for portName in getDeviceId";
612 AndroidSerialLog(), result, name.object<jstring>())) {
616 return static_cast<int>(result);
627 AndroidSerialLog(), result,
static_cast<jint
>(deviceId))) {
631 return static_cast<int>(result);
641 qCWarning(AndroidSerialLog) <<
"open called with null serialPort";
647 qCWarning(AndroidSerialLog) <<
"open called with unregistered pointer — call registerPointer first";
651 const QJniObject name = QJniObject::fromString(portName);
652 if (!name.isValid()) {
653 qCWarning(AndroidSerialLog) <<
"Invalid QJniObject for portName in open";
663 name.object<jstring>(), token)) {
667 return static_cast<int>(deviceId);
676 jboolean result = JNI_FALSE;
678 result,
static_cast<jint
>(deviceId))) {
682 return (result == JNI_TRUE);
687 const QJniObject name = QJniObject::fromString(portName);
688 if (!name.isValid()) {
689 qCWarning(AndroidSerialLog) <<
"Invalid QJniObject for portName in isOpen";
697 jboolean result = JNI_FALSE;
699 AndroidSerialLog(), result, name.object<jstring>())) {
703 return (result == JNI_TRUE);
710QByteArray
read(
int deviceId,
int length,
int timeout)
717 ctx.
env.jniEnv(),
static_cast<jbyteArray
>(
719 static_cast<jint
>(length),
static_cast<jint
>(timeout))));
722 qCWarning(AndroidSerialLog) <<
"read method returned null";
723 (void)ctx.
env.checkAndClearExceptions();
727 if (ctx.
env.checkAndClearExceptions()) {
728 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling read";
732 const jsize len = ctx.
env->GetArrayLength(jarray.
get());
733 jbyte*
const bytes = ctx.
env->GetByteArrayElements(jarray.
get(),
nullptr);
735 qCWarning(AndroidSerialLog) <<
"Failed to get byte array elements in read";
739 const QByteArray data(
reinterpret_cast<char*
>(bytes), len);
740 ctx.
env->ReleaseByteArrayElements(jarray.
get(), bytes, JNI_ABORT);
745int write(
int deviceId,
const char* data,
int length,
int timeout,
bool async)
747 if (!data || length <= 0) {
748 qCWarning(AndroidSerialLog) <<
"Invalid data or length in write";
757 ctx.
env->NewByteArray(
static_cast<jsize
>(length)));
759 qCWarning(AndroidSerialLog) <<
"Failed to create jbyteArray in write";
763 ctx.
env->SetByteArrayRegion(jarray.
get(), 0,
static_cast<jsize
>(length),
reinterpret_cast<const jbyte*
>(data));
764 if (ctx.
env.checkAndClearExceptions()) {
765 qCWarning(AndroidSerialLog) <<
"Exception occurred while setting byte array region in write";
772 static_cast<jint
>(timeout));
775 static_cast<jint
>(length),
static_cast<jint
>(timeout));
778 if (ctx.
env.checkAndClearExceptions()) {
779 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling write/writeAsync";
783 return static_cast<int>(result);
790bool setParameters(
int deviceId,
int baudRate,
int dataBits,
int stopBits,
int parity)
796 jboolean result = JNI_FALSE;
798 AndroidSerialLog(), result,
static_cast<jint
>(deviceId),
799 static_cast<jint
>(baudRate),
static_cast<jint
>(dataBits),
800 static_cast<jint
>(stopBits),
static_cast<jint
>(parity))) {
804 return (result == JNI_TRUE);
817 jboolean result = JNI_FALSE;
819 static_cast<jint
>(deviceId))) {
823 return (result == JNI_TRUE);
832 const jboolean jSet = set ? JNI_TRUE : JNI_FALSE;
833 jboolean result = JNI_FALSE;
835 static_cast<jint
>(deviceId), jSet)) {
839 return (result == JNI_TRUE);
890 return QSerialPort::PinoutSignals();
894 static_cast<jint
>(deviceId))));
896 qCWarning(AndroidSerialLog) <<
"getControlLines returned null";
897 (void)ctx.
env.checkAndClearExceptions();
898 return QSerialPort::PinoutSignals();
901 if (ctx.
env.checkAndClearExceptions()) {
902 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling getControlLines";
903 return QSerialPort::PinoutSignals();
906 jint*
const ints = ctx.
env->GetIntArrayElements(jarray.
get(),
nullptr);
908 qCWarning(AndroidSerialLog) <<
"Failed to get int array elements in getControlLines";
909 return QSerialPort::PinoutSignals();
912 const jsize len = ctx.
env->GetArrayLength(jarray.
get());
913 QSerialPort::PinoutSignals data = QSerialPort::PinoutSignals();
915 for (jsize i = 0; i < len; ++i) {
936 qCWarning(AndroidSerialLog) <<
"Unknown ControlLine value:" << ints[i];
941 ctx.
env->ReleaseIntArrayElements(jarray.
get(), ints, JNI_ABORT);
942 (void)ctx.
env.checkAndClearExceptions();
959 AndroidSerialLog(), flowControl,
static_cast<jint
>(deviceId))) {
963 return static_cast<int>(flowControl);
972 jboolean result = JNI_FALSE;
974 AndroidSerialLog(), result,
static_cast<jint
>(deviceId),
975 static_cast<jint
>(flowControl))) {
979 return result == JNI_TRUE;
992 const jboolean jInput = input ? JNI_TRUE : JNI_FALSE;
993 const jboolean jOutput = output ? JNI_TRUE : JNI_FALSE;
995 jboolean result = JNI_FALSE;
997 AndroidSerialLog(), result,
static_cast<jint
>(deviceId), jInput,
1002 return (result == JNI_TRUE);
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static QString portNameFromSystemLocation(const QString &source)
bool hasProductIdentifier
quint16 productIdentifier
void newDataArrived(const char *bytes, int length)
Provides functions to access serial ports.
@ DataTerminalReadySignal
@ DataCarrierDetectSignal
bool callStaticBooleanMethod(QJniEnvironment &env, jclass cls, jmethodID method, const char *caller, const QLoggingCategory &logCategory, jboolean &result, Args... args)
bool callStaticIntMethod(QJniEnvironment &env, jclass cls, jmethodID method, const char *caller, const QLoggingCategory &logCategory, jint &result, Args... args)
int open(const QString &portName, QSerialPortPrivate *classPtr)
static bool cacheMethodIds(JNIEnv *env, jclass javaClass)
bool getRingIndicator(int deviceId)
void registerPointer(QSerialPortPrivate *ptr)
QByteArray read(int deviceId, int length, int timeout)
static QSerialPortPrivate * lookupByToken(jlong token)
static jclass getSerialManagerClass()
int getDeviceHandle(int deviceId)
static bool s_methodsCached
QList< QSerialPortInfo > availableDevices()
static void jniDeviceNewData(JNIEnv *env, jobject obj, jlong token, jbyteArray data)
static bool callBoolMethod(jmethodID method, int deviceId, const char *name)
static void jniDeviceHasDisconnected(JNIEnv *env, jobject obj, jlong token)
bool getRequestToSend(int deviceId)
bool getDataTerminalReady(int deviceId)
constexpr const char * kJniUsbSerialManagerClassName
static bool getContext(JniContext &ctx, const char *caller)
static QMutex s_cacheLock
static void jniDeviceException(JNIEnv *env, jobject obj, jlong token, jstring message)
static jclass s_serialManagerClass
void unregisterPointer(QSerialPortPrivate *ptr)
static QSerialPort * lookupPortByTokenLocked(jlong token)
static JniMethodCache s_methods
int getDeviceId(const QString &portName)
bool setDataTerminalReady(int deviceId, bool set)
QList< QSerialPortInfo > availablePosixPorts()
static bool dispatchToPortObject(QSerialPort *serialPort, Functor &&func, const char *context)
bool setParameters(int deviceId, int baudRate, int dataBits, int stopBits, int parity)
bool startReadThread(int deviceId)
int getFlowControl(int deviceId)
static std::atomic< bool > s_usePosixSerial
QSerialPort::PinoutSignals getControlLines(int deviceId)
bool getDataSetReady(int deviceId)
static QHash< jlong, QSerialPortPrivate * > s_tokenToPtr
bool setRequestToSend(int deviceId, bool set)
bool getClearToSend(int deviceId)
bool purgeBuffers(int deviceId, bool input, bool output)
static QReadWriteLock s_ptrLock
bool getCarrierDetect(int deviceId)
bool isOpen(const QString &portName)
bool readThreadRunning(int deviceId)
bool stopReadThread(int deviceId)
static QHash< QSerialPortPrivate *, jlong > s_ptrToToken
void setUsePosixSerial(bool use)
bool hasPosixSerialPorts()
static jlong lookupToken(QSerialPortPrivate *ptr)
static bool callBoolSetMethod(jmethodID method, int deviceId, bool set, const char *name)
bool setFlowControl(int deviceId, int flowControl)
bool setBreak(int deviceId, bool set)
constexpr qint64 MAX_READ_SIZE
constexpr int INVALID_DEVICE_ID
jmethodID getRequestToSend
jmethodID availableDevicesInfo
jmethodID ioManagerRunning
jmethodID getDataSetReady
jmethodID setDataTerminalReady
jmethodID getCarrierDetect
jmethodID getDeviceHandle
jmethodID isDeviceNameOpen
jmethodID getDataTerminalReady
jmethodID getRingIndicator
jmethodID setRequestToSend
jmethodID getControlLines