QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
AndroidSerial.cc
Go to the documentation of this file.
1#include "AndroidSerial.h"
2
3#include <QtCore/QDir>
4#include <QtCore/QHash>
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>
12#include <qserialport_p.h>
13#include <qserialportinfo_p.h>
14
15#include <unistd.h>
16
17#include <atomic>
18#include <utility>
19
20#include "AndroidInterface.h"
21#include "QGCLoggingCategory.h"
22
23QGC_LOGGING_CATEGORY(AndroidSerialLog, "Android.AndroidSerial");
24
25namespace AndroidSerial {
26
27// ----------------------------------------------------------------------------
28// POSIX serial backend support
29// ----------------------------------------------------------------------------
30
31static std::atomic<bool> s_usePosixSerial{false};
32
33void setUsePosixSerial(bool use)
34{
35 s_usePosixSerial.store(use);
36 qCDebug(AndroidSerialLog) << "Serial backend:" << (use ? "POSIX" : "Java USB");
37}
38
40{
41 return s_usePosixSerial.load();
42}
43
44QList<QSerialPortInfo> availablePosixPorts()
45{
46 // Common SoC UART device node name patterns
47 static const QStringList kUartPatterns = {
48 QStringLiteral("ttyS*"), QStringLiteral("ttyHS*"), QStringLiteral("ttyMSM*"),
49 QStringLiteral("ttyHSL*"), QStringLiteral("ttymxc*"), QStringLiteral("ttyAMA*"),
50 QStringLiteral("ttyTHS*"),
51 };
52
53 QList<QSerialPortInfo> serialPortInfoList;
54
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) {
60 continue;
61 }
62
64 info.portName = deviceName;
65 info.device = systemLocation;
66 serialPortInfoList.append(info);
67 }
68
69 return serialPortInfoList;
70}
71
73{
74 return !availablePosixPorts().isEmpty();
75}
76
77// ----------------------------------------------------------------------------
78// Token-based pointer tracking (UAF protection)
79//
80// Java receives an opaque random jlong token instead of a raw C++ pointer.
81// A bidirectional hash map under QReadWriteLock maps tokens ↔ pointers.
82// JNI callbacks (readers) take a shared read lock; register/unregister
83// (writers) take an exclusive write lock. Pattern follows Qt Bluetooth's
84// LowEnergyNotificationHub.
85// ----------------------------------------------------------------------------
86
87static QReadWriteLock s_ptrLock;
88static QHash<jlong, QSerialPortPrivate*> s_tokenToPtr;
89static QHash<QSerialPortPrivate*, jlong> s_ptrToToken;
90
92{
93 if (!ptr) {
94 qCWarning(AndroidSerialLog) << "registerPointer called with null pointer";
95 return;
96 }
97
98 QWriteLocker locker(&s_ptrLock);
99
100 const auto existingIt = s_ptrToToken.constFind(ptr);
101 if (existingIt != s_ptrToToken.cend()) {
102 s_tokenToPtr.remove(*existingIt);
103 s_ptrToToken.erase(existingIt);
104 }
105
106 jlong token;
107 do {
108 token = static_cast<jlong>(QRandomGenerator::global()->generate64());
109 } while (token == 0 || s_tokenToPtr.contains(token));
110
111 s_tokenToPtr.insert(token, ptr);
112 s_ptrToToken.insert(ptr, token);
113}
114
116{
117 if (!ptr) {
118 return;
119 }
120
121 QWriteLocker locker(&s_ptrLock);
122 const jlong token = s_ptrToToken.take(ptr);
123 if (token == 0) {
124 return;
125 }
126 s_tokenToPtr.remove(token);
127}
128
130{
131 QReadLocker locker(&s_ptrLock);
132 return s_tokenToPtr.value(token, nullptr);
133}
134
136{
137 QReadLocker locker(&s_ptrLock);
138 return s_ptrToToken.value(ptr, 0);
139}
140
142{
143 QSerialPortPrivate* const serialPortPrivate = s_tokenToPtr.value(token, nullptr);
144 if (!serialPortPrivate) {
145 return nullptr;
146 }
147
148 return qobject_cast<QSerialPort*>(serialPortPrivate->q_ptr);
149}
150
151template <typename Functor>
152static bool dispatchToPortObject(QSerialPort* serialPort, Functor&& func, const char* context)
153{
154 if (!serialPort) {
155 qCWarning(AndroidSerialLog) << context << ": null serial port";
156 return false;
157 }
158
159 QThread* const targetThread = serialPort->thread();
160 const bool sameThread = (targetThread == QThread::currentThread());
161 const bool hasEventLoop = targetThread && targetThread->eventDispatcher();
162
163 if (sameThread) {
164 std::forward<Functor>(func)();
165 return true;
166 }
167
168 if (hasEventLoop) {
169 // BlockingQueuedConnection ensures the operation completes on the target thread
170 // before returning to the JNI caller (e.g. device disconnect is fully processed
171 // before Java-side cleanup continues).
172 const bool ok = QMetaObject::invokeMethod(serialPort, std::forward<Functor>(func), Qt::BlockingQueuedConnection);
173 if (!ok) {
174 qCWarning(AndroidSerialLog) << context << ": failed to invoke method on target thread";
175 }
176 return ok;
177 }
178
179 qCWarning(AndroidSerialLog) << context << ": target thread has no event loop, running inline fallback";
180 std::forward<Functor>(func)();
181 return true;
182}
183
184// ----------------------------------------------------------------------------
185// JNI method ID cache
186// ----------------------------------------------------------------------------
187
189{
190 jmethodID availableDevicesInfo = nullptr;
191 jmethodID getDeviceId = nullptr;
192 jmethodID getDeviceHandle = nullptr;
193 jmethodID open = nullptr;
194 jmethodID close = nullptr;
195 jmethodID isDeviceNameOpen = nullptr;
196 jmethodID read = nullptr;
197 jmethodID write = nullptr;
198 jmethodID writeWithProgress = nullptr;
199 jmethodID writeAsync = nullptr;
200 jmethodID setParameters = nullptr;
201 jmethodID getCarrierDetect = nullptr;
202 jmethodID getClearToSend = nullptr;
203 jmethodID getDataSetReady = nullptr;
204 jmethodID getDataTerminalReady = nullptr;
205 jmethodID setDataTerminalReady = nullptr;
206 jmethodID dataTerminalReadySupport = nullptr;
207 jmethodID getRingIndicator = nullptr;
208 jmethodID getRequestToSend = nullptr;
209 jmethodID setRequestToSend = nullptr;
210 jmethodID requestToSendSupport = nullptr;
211 jmethodID getControlLines = nullptr;
212 jmethodID getFlowControl = nullptr;
213 jmethodID setFlowControl = nullptr;
214 jmethodID purgeBuffers = nullptr;
215 jmethodID setBreak = nullptr;
216 jmethodID startIoManager = nullptr;
217 jmethodID stopIoManager = nullptr;
218 jmethodID ioManagerRunning = nullptr;
219};
220
222static bool s_methodsCached = false;
223static QMutex s_cacheLock;
224static jclass s_serialManagerClass = nullptr;
225
226static bool cacheMethodIds(JNIEnv* env, jclass javaClass)
227{
228 struct MethodDef
229 {
230 jmethodID* target;
231 const char* name;
232 const char* sig;
233 };
234
235 const MethodDef defs[] = {
236 {&s_methods.availableDevicesInfo, "availableDevicesInfo", "()[Ljava/lang/String;"},
237 {&s_methods.getDeviceId, "getDeviceId", "(Ljava/lang/String;)I"},
238 {&s_methods.getDeviceHandle, "getDeviceHandle", "(I)I"},
239 {&s_methods.open, "open", "(Ljava/lang/String;J)I"},
240 {&s_methods.close, "close", "(I)Z"},
241 {&s_methods.isDeviceNameOpen, "isDeviceNameOpen", "(Ljava/lang/String;)Z"},
242 {&s_methods.read, "read", "(III)[B"},
243 {&s_methods.write, "write", "(I[BII)I"},
244 {&s_methods.writeWithProgress, "writeWithProgress", "(I[BII)I"},
245 {&s_methods.writeAsync, "writeAsync", "(I[BI)I"},
246 {&s_methods.setParameters, "setParameters", "(IIIII)Z"},
247 {&s_methods.getCarrierDetect, "getCarrierDetect", "(I)Z"},
248 {&s_methods.getClearToSend, "getClearToSend", "(I)Z"},
249 {&s_methods.getDataSetReady, "getDataSetReady", "(I)Z"},
250 {&s_methods.getDataTerminalReady, "getDataTerminalReady", "(I)Z"},
251 {&s_methods.setDataTerminalReady, "setDataTerminalReady", "(IZ)Z"},
252 {&s_methods.dataTerminalReadySupport, "getDataTerminalReadySupport", "(I)I"},
253 {&s_methods.getRingIndicator, "getRingIndicator", "(I)Z"},
254 {&s_methods.getRequestToSend, "getRequestToSend", "(I)Z"},
255 {&s_methods.setRequestToSend, "setRequestToSend", "(IZ)Z"},
256 {&s_methods.requestToSendSupport, "getRequestToSendSupport", "(I)I"},
257 {&s_methods.getControlLines, "getControlLines", "(I)[I"},
258 {&s_methods.getFlowControl, "getFlowControl", "(I)I"},
259 {&s_methods.setFlowControl, "setFlowControl", "(II)Z"},
260 {&s_methods.purgeBuffers, "purgeBuffers", "(IZZ)Z"},
261 {&s_methods.setBreak, "setBreak", "(IZ)Z"},
262 {&s_methods.startIoManager, "startIoManager", "(I)Z"},
263 {&s_methods.stopIoManager, "stopIoManager", "(I)Z"},
264 {&s_methods.ioManagerRunning, "ioManagerRunning", "(I)Z"},
265 };
266
267 for (const auto& def : defs) {
268 *def.target = env->GetStaticMethodID(javaClass, def.name, def.sig);
269 if (!*def.target) {
270 qCWarning(AndroidSerialLog) << "Failed to cache method:" << def.name << def.sig;
271 (void)QJniEnvironment::checkAndClearExceptions(env);
272 return false;
273 }
274 }
275
276 s_methodsCached = true;
277 qCDebug(AndroidSerialLog) << "All JNI method IDs cached successfully";
278 return true;
279}
280
281// ----------------------------------------------------------------------------
282// Class resolution
283// ----------------------------------------------------------------------------
284
286{
287 QMutexLocker locker(&s_cacheLock);
288
291 }
292
293 QJniEnvironment env;
294 if (!env.isValid()) {
295 qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment";
296 return nullptr;
297 }
298
300 const jclass resolvedClass = env.findClass(kJniUsbSerialManagerClassName);
301 if (!resolvedClass) {
302 qCWarning(AndroidSerialLog) << "Class Not Found:" << kJniUsbSerialManagerClassName;
303 return nullptr;
304 }
305
306 s_serialManagerClass = static_cast<jclass>(env->NewGlobalRef(resolvedClass));
307 if (env->GetObjectRefType(resolvedClass) == JNILocalRefType) {
308 env->DeleteLocalRef(resolvedClass);
309 }
310
312 qCWarning(AndroidSerialLog) << "Failed to create global ref for class:" << kJniUsbSerialManagerClassName;
313 (void)env.checkAndClearExceptions();
314 return nullptr;
315 }
316 }
317
318 if (!s_methodsCached && !cacheMethodIds(env.jniEnv(), s_serialManagerClass)) {
319 qCWarning(AndroidSerialLog) << "Failed to cache JNI method IDs";
320 env->DeleteGlobalRef(s_serialManagerClass);
321 s_serialManagerClass = nullptr;
322 s_methods = {};
323 (void)env.checkAndClearExceptions();
324 return nullptr;
325 }
326
327 s_methodsCached = true;
328 (void)env.checkAndClearExceptions();
330}
331
333{
334 QMutexLocker locker(&s_cacheLock);
335 QJniEnvironment env;
336 if (s_serialManagerClass && env.isValid()) {
337 env->DeleteGlobalRef(s_serialManagerClass);
338 }
339 s_serialManagerClass = nullptr;
340 s_methods = {};
341 s_methodsCached = false;
342}
343
344// ----------------------------------------------------------------------------
345// Native method registration
346// ----------------------------------------------------------------------------
347
348// Forward declarations for JNI callbacks (defined below)
349static void jniDeviceHasDisconnected(JNIEnv* env, jobject obj, jlong token);
350static void jniDeviceNewData(JNIEnv* env, jobject obj, jlong token, jbyteArray data);
351static void jniDeviceException(JNIEnv* env, jobject obj, jlong token, jstring message);
352
354{
355 qCDebug(AndroidSerialLog) << "Registering Native Functions";
356
357 const JNINativeMethod javaMethods[]{
358 {"nativeDeviceHasDisconnected", "(J)V", reinterpret_cast<void*>(jniDeviceHasDisconnected)},
359 {"nativeDeviceNewData", "(J[B)V", reinterpret_cast<void*>(jniDeviceNewData)},
360 {"nativeDeviceException", "(JLjava/lang/String;)V", reinterpret_cast<void*>(jniDeviceException)},
361 };
362
363 QJniEnvironment env;
364 if (!env.registerNativeMethods(kJniUsbSerialManagerClassName, javaMethods, std::size(javaMethods))) {
365 qCWarning(AndroidSerialLog) << "Failed to register native methods for" << kJniUsbSerialManagerClassName;
366 return;
367 }
368
369 if (!getSerialManagerClass()) {
370 qCWarning(AndroidSerialLog) << "Failed to cache JNI method IDs";
371 return;
372 }
373
374 qCDebug(AndroidSerialLog) << "Native Functions Registered Successfully";
375}
376
377// ----------------------------------------------------------------------------
378// JNI callbacks (called from Java threads)
379// ----------------------------------------------------------------------------
380
381static void jniDeviceHasDisconnected(JNIEnv*, jobject, jlong token)
382{
383 if (token == 0) {
384 qCWarning(AndroidSerialLog) << "nativeDeviceHasDisconnected called with token=0";
385 return;
386 }
387
388 QPointer<QSerialPort> serialPort;
389 {
390 QReadLocker locker(&s_ptrLock);
391 serialPort = lookupPortByTokenLocked(token);
392 if (!serialPort) {
393 qCWarning(AndroidSerialLog) << "nativeDeviceHasDisconnected: stale token, object already destroyed";
394 return;
395 }
396 qCDebug(AndroidSerialLog) << "Device disconnected:" << serialPort->portName();
397 }
398
400 serialPort.data(),
401 [token]() {
402 QSerialPortPrivate* const p = lookupByToken(token);
403 if (!p) {
404 qCDebug(AndroidSerialLog) << "Token already invalidated in nativeDeviceHasDisconnected";
405 return;
406 }
407
408 QSerialPort* const port = qobject_cast<QSerialPort*>(p->q_ptr);
409 if (port && port->isOpen()) {
410 port->close();
411 qCDebug(AndroidSerialLog) << "Serial port closed in nativeDeviceHasDisconnected";
412 } else {
413 qCDebug(AndroidSerialLog) << "Serial port was already closed in nativeDeviceHasDisconnected";
414 }
415 },
416 "nativeDeviceHasDisconnected")) {
417 qCWarning(AndroidSerialLog) << "nativeDeviceHasDisconnected: failed to dispatch cleanup";
418 }
419}
420
421static void jniDeviceNewData(JNIEnv* env, jobject, jlong token, jbyteArray data)
422{
423 constexpr jsize kMaxNativePayloadBytes = static_cast<jsize>(MAX_READ_SIZE);
424
425 if (token == 0) {
426 qCWarning(AndroidSerialLog) << "nativeDeviceNewData called with token=0";
427 return;
428 }
429
430 if (!data) {
431 qCWarning(AndroidSerialLog) << "nativeDeviceNewData called with null data";
432 return;
433 }
434
435 const jsize len = env->GetArrayLength(data);
436 if (len <= 0) {
437 qCWarning(AndroidSerialLog) << "nativeDeviceNewData received empty data array";
438 return;
439 }
440
441 const jsize cappedLen = (len > kMaxNativePayloadBytes) ? kMaxNativePayloadBytes : len;
442 if (cappedLen != len) {
443 qCWarning(AndroidSerialLog) << "nativeDeviceNewData payload exceeds limit, truncating from" << len << "to"
444 << cappedLen << "bytes";
445 }
446
447 QByteArray payload(cappedLen, Qt::Uninitialized);
448 env->GetByteArrayRegion(data, 0, cappedLen, reinterpret_cast<jbyte*>(payload.data()));
449 if (QJniEnvironment::checkAndClearExceptions(env)) {
450 qCWarning(AndroidSerialLog) << "nativeDeviceNewData failed to copy JNI byte array";
451 return;
452 }
453
454 {
455 // Deliver inline while holding read lock so unregister/destroy cannot
456 // invalidate the pointer until this handoff is complete.
457 QReadLocker locker(&s_ptrLock);
458 QSerialPortPrivate* const serialPortPrivate = s_tokenToPtr.value(token, nullptr);
459 if (!serialPortPrivate) {
460 qCWarning(AndroidSerialLog) << "nativeDeviceNewData: stale token, object already destroyed";
461 return;
462 }
463
464 serialPortPrivate->newDataArrived(payload.constData(), payload.size());
465 }
466}
467
468static void jniDeviceException(JNIEnv*, jobject, jlong token, jstring message)
469{
470 if (token == 0) {
471 qCWarning(AndroidSerialLog) << "nativeDeviceException called with token=0";
472 return;
473 }
474
475 if (!message) {
476 qCWarning(AndroidSerialLog) << "nativeDeviceException called with null message";
477 return;
478 }
479
480 const QString exceptionMessage = QJniObject(message).toString();
481
482 QPointer<QSerialPort> serialPort;
483 {
484 QReadLocker locker(&s_ptrLock);
485 serialPort = lookupPortByTokenLocked(token);
486 if (!serialPort) {
487 qCWarning(AndroidSerialLog) << "nativeDeviceException: stale token, object already destroyed";
488 return;
489 }
490 }
491
492 qCWarning(AndroidSerialLog) << "Exception from Java:" << exceptionMessage;
493
495 serialPort.data(),
496 [token, exceptionMessage]() {
497 QSerialPortPrivate* const p = lookupByToken(token);
498 if (!p) {
499 qCDebug(AndroidSerialLog) << "Token already invalidated in nativeDeviceException";
500 return;
501 }
502
503 p->exceptionArrived(exceptionMessage);
504 },
505 "nativeDeviceException")) {
506 qCWarning(AndroidSerialLog) << "nativeDeviceException: failed to dispatch exception callback";
507 }
508}
509
510// ----------------------------------------------------------------------------
511// Helper: get env + class + check cached method in one shot
512// ----------------------------------------------------------------------------
513
515{
516 QJniEnvironment env;
517 jclass cls = nullptr;
518 bool valid = false;
519};
520
521static bool getContext(JniContext& ctx, const char* caller)
522{
523 if (!ctx.env.isValid()) {
524 qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in" << caller;
525 return false;
526 }
527
529 if (!ctx.cls) {
530 qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in" << caller;
531 return false;
532 }
533
534 ctx.valid = true;
535 return true;
536}
537
538// ----------------------------------------------------------------------------
539// Device enumeration
540// ----------------------------------------------------------------------------
541
542QList<QSerialPortInfo> availableDevices()
543{
544 QList<QSerialPortInfo> serialPortInfoList;
545
546 JniContext ctx;
547 if (!getContext(ctx, "availableDevices"))
548 return serialPortInfoList;
549
551 ctx.env.jniEnv(),
552 static_cast<jobjectArray>(ctx.env->CallStaticObjectMethod(ctx.cls, s_methods.availableDevicesInfo)));
553 if (!objArray.get()) {
554 qCDebug(AndroidSerialLog) << "availableDevicesInfo returned null";
555 (void)ctx.env.checkAndClearExceptions();
556 return serialPortInfoList;
557 }
558
559 if (ctx.env.checkAndClearExceptions()) {
560 qCWarning(AndroidSerialLog) << "Exception occurred while calling availableDevicesInfo";
561 return serialPortInfoList;
562 }
563
564 const jsize count = ctx.env->GetArrayLength(objArray.get());
565 for (jsize i = 0; i < count; ++i) {
567 ctx.env.jniEnv(), static_cast<jstring>(ctx.env->GetObjectArrayElement(objArray.get(), i)));
568 if (!jstr.get()) {
569 qCWarning(AndroidSerialLog) << "Null string at index" << i;
570 continue;
571 }
572
573 const QStringList strList = QJniObject(jstr.get()).toString().split(QLatin1Char('\t'));
574
575 if (strList.size() < 6) {
576 qCWarning(AndroidSerialLog) << "Invalid device info at index" << i << ":" << strList;
577 continue;
578 }
579
580 bool pidOK, vidOK;
583 info.device = strList[0];
584 info.description = strList[1];
585 info.manufacturer = strList[2];
586 info.serialNumber = strList[3];
587 info.productIdentifier = strList[4].toInt(&pidOK);
588 info.hasProductIdentifier = (pidOK && (info.productIdentifier != INVALID_DEVICE_ID));
589 info.vendorIdentifier = strList[5].toInt(&vidOK);
590 info.hasVendorIdentifier = (vidOK && (info.vendorIdentifier != INVALID_DEVICE_ID));
591
592 serialPortInfoList.append(info);
593 }
594
595 (void)ctx.env.checkAndClearExceptions();
596
597 return serialPortInfoList;
598}
599
600// ----------------------------------------------------------------------------
601// Device ID / handle lookup
602// ----------------------------------------------------------------------------
603
604int getDeviceId(const QString& portName)
605{
606 const QJniObject name = QJniObject::fromString(portName);
607 if (!name.isValid()) {
608 qCWarning(AndroidSerialLog) << "Invalid QJniObject for portName in getDeviceId";
609 return -1;
610 }
611
612 JniContext ctx;
613 if (!getContext(ctx, "getDeviceId"))
614 return -1;
615
616 jint result = -1;
618 AndroidSerialLog(), result, name.object<jstring>())) {
619 return -1;
620 }
621
622 return static_cast<int>(result);
623}
624
625int getDeviceHandle(int deviceId)
626{
627 JniContext ctx;
628 if (!getContext(ctx, "getDeviceHandle"))
629 return -1;
630
631 jint result = -1;
633 AndroidSerialLog(), result, static_cast<jint>(deviceId))) {
634 return -1;
635 }
636
637 return static_cast<int>(result);
638}
639
640// ----------------------------------------------------------------------------
641// Open / close / isOpen
642// ----------------------------------------------------------------------------
643
644int open(const QString& portName, QSerialPortPrivate* classPtr)
645{
646 if (!classPtr) {
647 qCWarning(AndroidSerialLog) << "open called with null serialPort";
648 return INVALID_DEVICE_ID;
649 }
650
651 const jlong token = lookupToken(classPtr);
652 if (token == 0) {
653 qCWarning(AndroidSerialLog) << "open called with unregistered pointer — call registerPointer first";
654 return INVALID_DEVICE_ID;
655 }
656
657 const QJniObject name = QJniObject::fromString(portName);
658 if (!name.isValid()) {
659 qCWarning(AndroidSerialLog) << "Invalid QJniObject for portName in open";
660 return INVALID_DEVICE_ID;
661 }
662
663 JniContext ctx;
664 if (!getContext(ctx, "open"))
665 return INVALID_DEVICE_ID;
666
667 jint deviceId = INVALID_DEVICE_ID;
668 if (!AndroidInterface::callStaticIntMethod(ctx.env, ctx.cls, s_methods.open, "open", AndroidSerialLog(), deviceId,
669 name.object<jstring>(), token)) {
670 return INVALID_DEVICE_ID;
671 }
672
673 return static_cast<int>(deviceId);
674}
675
676bool close(int deviceId)
677{
678 JniContext ctx;
679 if (!getContext(ctx, "close"))
680 return false;
681
682 jboolean result = JNI_FALSE;
683 if (!AndroidInterface::callStaticBooleanMethod(ctx.env, ctx.cls, s_methods.close, "close", AndroidSerialLog(),
684 result, static_cast<jint>(deviceId))) {
685 return false;
686 }
687
688 return (result == JNI_TRUE);
689}
690
691bool isOpen(const QString& portName)
692{
693 const QJniObject name = QJniObject::fromString(portName);
694 if (!name.isValid()) {
695 qCWarning(AndroidSerialLog) << "Invalid QJniObject for portName in isOpen";
696 return false;
697 }
698
699 JniContext ctx;
700 if (!getContext(ctx, "isOpen"))
701 return false;
702
703 jboolean result = JNI_FALSE;
705 AndroidSerialLog(), result, name.object<jstring>())) {
706 return false;
707 }
708
709 return (result == JNI_TRUE);
710}
711
712// ----------------------------------------------------------------------------
713// Read / write
714// ----------------------------------------------------------------------------
715
716QByteArray read(int deviceId, int length, int timeout)
717{
718 JniContext ctx;
719 if (!getContext(ctx, "read"))
720 return QByteArray();
721
723 ctx.env.jniEnv(), static_cast<jbyteArray>(
724 ctx.env->CallStaticObjectMethod(ctx.cls, s_methods.read, static_cast<jint>(deviceId),
725 static_cast<jint>(length), static_cast<jint>(timeout))));
726
727 if (!jarray.get()) {
728 qCWarning(AndroidSerialLog) << "read method returned null";
729 (void)ctx.env.checkAndClearExceptions();
730 return QByteArray();
731 }
732
733 if (ctx.env.checkAndClearExceptions()) {
734 qCWarning(AndroidSerialLog) << "Exception occurred while calling read";
735 return QByteArray();
736 }
737
738 const jsize len = ctx.env->GetArrayLength(jarray.get());
739 jbyte* const bytes = ctx.env->GetByteArrayElements(jarray.get(), nullptr);
740 if (!bytes) {
741 qCWarning(AndroidSerialLog) << "Failed to get byte array elements in read";
742 return QByteArray();
743 }
744
745 const QByteArray data(reinterpret_cast<char*>(bytes), len);
746 ctx.env->ReleaseByteArrayElements(jarray.get(), bytes, JNI_ABORT);
747
748 return data;
749}
750
751int write(int deviceId, const char* data, int length, int timeout, bool async)
752{
753 if (!data || length <= 0) {
754 qCWarning(AndroidSerialLog) << "Invalid data or length in write";
755 return -1;
756 }
757
758 JniContext ctx;
759 if (!getContext(ctx, "write"))
760 return -1;
761
763 ctx.env->NewByteArray(static_cast<jsize>(length)));
764 if (!jarray.get()) {
765 qCWarning(AndroidSerialLog) << "Failed to create jbyteArray in write";
766 return -1;
767 }
768
769 ctx.env->SetByteArrayRegion(jarray.get(), 0, static_cast<jsize>(length), reinterpret_cast<const jbyte*>(data));
770 if (ctx.env.checkAndClearExceptions()) {
771 qCWarning(AndroidSerialLog) << "Exception occurred while setting byte array region in write";
772 return -1;
773 }
774
775 jint result;
776 if (async) {
777 result = ctx.env->CallStaticIntMethod(ctx.cls, s_methods.writeAsync, static_cast<jint>(deviceId), jarray.get(),
778 static_cast<jint>(timeout));
779 } else {
780 result = ctx.env->CallStaticIntMethod(ctx.cls, s_methods.write, static_cast<jint>(deviceId), jarray.get(),
781 static_cast<jint>(length), static_cast<jint>(timeout));
782 }
783
784 if (ctx.env.checkAndClearExceptions()) {
785 qCWarning(AndroidSerialLog) << "Exception occurred while calling write/writeAsync";
786 return -1;
787 }
788
789 return static_cast<int>(result);
790}
791
792int writeWithProgress(int deviceId, const char* data, int length, int timeout)
793{
794 if (!data || length <= 0) {
795 qCWarning(AndroidSerialLog) << "Invalid data or length in writeWithProgress";
796 return -1;
797 }
798
799 JniContext ctx;
800 if (!getContext(ctx, "writeWithProgress"))
801 return -1;
802
804 ctx.env->NewByteArray(static_cast<jsize>(length)));
805 if (!jarray.get()) {
806 qCWarning(AndroidSerialLog) << "Failed to create jbyteArray in writeWithProgress";
807 return -1;
808 }
809
810 ctx.env->SetByteArrayRegion(jarray.get(), 0, static_cast<jsize>(length), reinterpret_cast<const jbyte*>(data));
811 if (ctx.env.checkAndClearExceptions()) {
812 qCWarning(AndroidSerialLog) << "Exception occurred while setting byte array region in writeWithProgress";
813 return -1;
814 }
815
816 const jint result =
817 ctx.env->CallStaticIntMethod(ctx.cls, s_methods.writeWithProgress, static_cast<jint>(deviceId), jarray.get(),
818 static_cast<jint>(length), static_cast<jint>(timeout));
819
820 if (ctx.env.checkAndClearExceptions()) {
821 qCWarning(AndroidSerialLog) << "Exception occurred while calling writeWithProgress";
822 return -1;
823 }
824
825 return static_cast<int>(result);
826}
827
828// ----------------------------------------------------------------------------
829// Port configuration
830// ----------------------------------------------------------------------------
831
832bool setParameters(int deviceId, int baudRate, int dataBits, int stopBits, int parity)
833{
834 JniContext ctx;
835 if (!getContext(ctx, "setParameters"))
836 return false;
837
838 jboolean result = JNI_FALSE;
840 AndroidSerialLog(), result, static_cast<jint>(deviceId),
841 static_cast<jint>(baudRate), static_cast<jint>(dataBits),
842 static_cast<jint>(stopBits), static_cast<jint>(parity))) {
843 return false;
844 }
845
846 return (result == JNI_TRUE);
847}
848
849// ----------------------------------------------------------------------------
850// Control line helpers (DRY macro for bool getters)
851// ----------------------------------------------------------------------------
852
853static bool callBoolMethod(jmethodID method, int deviceId, const char* name)
854{
855 JniContext ctx;
856 if (!getContext(ctx, name))
857 return false;
858
859 jboolean result = JNI_FALSE;
860 if (!AndroidInterface::callStaticBooleanMethod(ctx.env, ctx.cls, method, name, AndroidSerialLog(), result,
861 static_cast<jint>(deviceId))) {
862 return false;
863 }
864
865 return (result == JNI_TRUE);
866}
867
868static bool callBoolSetMethod(jmethodID method, int deviceId, bool set, const char* name)
869{
870 JniContext ctx;
871 if (!getContext(ctx, name))
872 return false;
873
874 const jboolean jSet = set ? JNI_TRUE : JNI_FALSE;
875 jboolean result = JNI_FALSE;
876 if (!AndroidInterface::callStaticBooleanMethod(ctx.env, ctx.cls, method, name, AndroidSerialLog(), result,
877 static_cast<jint>(deviceId), jSet)) {
878 return false;
879 }
880
881 return (result == JNI_TRUE);
882}
883
884// ----------------------------------------------------------------------------
885// Control lines
886// ----------------------------------------------------------------------------
887
888bool getCarrierDetect(int deviceId)
889{
890 return callBoolMethod(s_methods.getCarrierDetect, deviceId, "getCarrierDetect");
891}
892
893bool getClearToSend(int deviceId)
894{
895 return callBoolMethod(s_methods.getClearToSend, deviceId, "getClearToSend");
896}
897
898bool getDataSetReady(int deviceId)
899{
900 return callBoolMethod(s_methods.getDataSetReady, deviceId, "getDataSetReady");
901}
902
903bool getDataTerminalReady(int deviceId)
904{
905 return callBoolMethod(s_methods.getDataTerminalReady, deviceId, "getDataTerminalReady");
906}
907
908bool getRingIndicator(int deviceId)
909{
910 return callBoolMethod(s_methods.getRingIndicator, deviceId, "getRingIndicator");
911}
912
913bool getRequestToSend(int deviceId)
914{
915 return callBoolMethod(s_methods.getRequestToSend, deviceId, "getRequestToSend");
916}
917
918bool setDataTerminalReady(int deviceId, bool set)
919{
920 return callBoolSetMethod(s_methods.setDataTerminalReady, deviceId, set, "setDataTerminalReady");
921}
922
924{
925 JniContext ctx;
926 if (!getContext(ctx, "getDataTerminalReadySupport"))
927 return -1;
928 jint result = -1;
930 "getDataTerminalReadySupport", AndroidSerialLog(), result, deviceId)) {
931 return -1;
932 }
933 return static_cast<int>(result);
934}
935
936bool setRequestToSend(int deviceId, bool set)
937{
938 return callBoolSetMethod(s_methods.setRequestToSend, deviceId, set, "setRequestToSend");
939}
940
941int requestToSendSupport(int deviceId)
942{
943 JniContext ctx;
944 if (!getContext(ctx, "getRequestToSendSupport")) {
945 return -1;
946 }
947 jint result = -1;
949 "getRequestToSendSupport", AndroidSerialLog(), result, deviceId)) {
950 return -1;
951 }
952 return static_cast<int>(result);
953}
954
955QSerialPort::PinoutSignals getControlLines(int deviceId)
956{
957 JniContext ctx;
958 if (!getContext(ctx, "getControlLines"))
959 return QSerialPort::PinoutSignals();
960
962 ctx.env.jniEnv(), static_cast<jintArray>(ctx.env->CallStaticObjectMethod(ctx.cls, s_methods.getControlLines,
963 static_cast<jint>(deviceId))));
964 if (!jarray.get()) {
965 qCWarning(AndroidSerialLog) << "getControlLines returned null";
966 (void)ctx.env.checkAndClearExceptions();
967 return QSerialPort::PinoutSignals();
968 }
969
970 if (ctx.env.checkAndClearExceptions()) {
971 qCWarning(AndroidSerialLog) << "Exception occurred while calling getControlLines";
972 return QSerialPort::PinoutSignals();
973 }
974
975 jint* const ints = ctx.env->GetIntArrayElements(jarray.get(), nullptr);
976 if (!ints) {
977 qCWarning(AndroidSerialLog) << "Failed to get int array elements in getControlLines";
978 return QSerialPort::PinoutSignals();
979 }
980
981 const jsize len = ctx.env->GetArrayLength(jarray.get());
982 QSerialPort::PinoutSignals data = QSerialPort::PinoutSignals();
983
984 for (jsize i = 0; i < len; ++i) {
985 switch (ints[i]) {
986 case RtsControlLine:
988 break;
989 case CtsControlLine:
991 break;
992 case DtrControlLine:
994 break;
995 case DsrControlLine:
997 break;
998 case CdControlLine:
1000 break;
1001 case RiControlLine:
1003 break;
1004 default:
1005 qCWarning(AndroidSerialLog) << "Unknown ControlLine value:" << ints[i];
1006 break;
1007 }
1008 }
1009
1010 ctx.env->ReleaseIntArrayElements(jarray.get(), ints, JNI_ABORT);
1011 (void)ctx.env.checkAndClearExceptions();
1012
1013 return data;
1014}
1015
1016// ----------------------------------------------------------------------------
1017// Flow control
1018// ----------------------------------------------------------------------------
1019
1020int getFlowControl(int deviceId)
1021{
1022 JniContext ctx;
1023 if (!getContext(ctx, "getFlowControl"))
1025
1026 jint flowControl = QSerialPort::NoFlowControl;
1027 if (!AndroidInterface::callStaticIntMethod(ctx.env, ctx.cls, s_methods.getFlowControl, "getFlowControl",
1028 AndroidSerialLog(), flowControl, static_cast<jint>(deviceId))) {
1030 }
1031
1032 return static_cast<int>(flowControl);
1033}
1034
1035bool setFlowControl(int deviceId, int flowControl)
1036{
1037 JniContext ctx;
1038 if (!getContext(ctx, "setFlowControl"))
1039 return false;
1040
1041 jboolean result = JNI_FALSE;
1043 AndroidSerialLog(), result, static_cast<jint>(deviceId),
1044 static_cast<jint>(flowControl))) {
1045 return false;
1046 }
1047
1048 return result == JNI_TRUE;
1049}
1050
1051// ----------------------------------------------------------------------------
1052// Buffer / break
1053// ----------------------------------------------------------------------------
1054
1055bool purgeBuffers(int deviceId, bool input, bool output)
1056{
1057 JniContext ctx;
1058 if (!getContext(ctx, "purgeBuffers"))
1059 return false;
1060
1061 const jboolean jInput = input ? JNI_TRUE : JNI_FALSE;
1062 const jboolean jOutput = output ? JNI_TRUE : JNI_FALSE;
1063
1064 jboolean result = JNI_FALSE;
1066 AndroidSerialLog(), result, static_cast<jint>(deviceId), jInput,
1067 jOutput)) {
1068 return false;
1069 }
1070
1071 return (result == JNI_TRUE);
1072}
1073
1074bool setBreak(int deviceId, bool set)
1075{
1076 return callBoolSetMethod(s_methods.setBreak, deviceId, set, "setBreak");
1077}
1078
1079// ----------------------------------------------------------------------------
1080// IO manager (read thread)
1081// ----------------------------------------------------------------------------
1082
1083bool startReadThread(int deviceId)
1084{
1085 return callBoolMethod(s_methods.startIoManager, deviceId, "startIoManager");
1086}
1087
1088bool stopReadThread(int deviceId)
1089{
1090 return callBoolMethod(s_methods.stopIoManager, deviceId, "stopIoManager");
1091}
1092
1093bool readThreadRunning(int deviceId)
1094{
1095 return callBoolMethod(s_methods.ioManagerRunning, deviceId, "ioManagerRunning");
1096}
1097
1098} // namespace AndroidSerial
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static QString portNameFromSystemLocation(const QString &source)
void newDataArrived(const char *bytes, int length)
Provides functions to access serial ports.
Definition qserialport.h:17
@ DataTerminalReadySignal
Definition qserialport.h:97
@ DataCarrierDetectSignal
Definition qserialport.h:98
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
int writeWithProgress(int deviceId, const char *data, int length, int timeout)
Returns bytes written, fewer than length on timeout, or -1 on error. A zero timeout waits without lim...
void unregisterPointer(QSerialPortPrivate *ptr)
static QSerialPort * lookupPortByTokenLocked(jlong token)
static JniMethodCache s_methods
int getDeviceId(const QString &portName)
bool usePosixSerial()
bool setDataTerminalReady(int deviceId, bool set)
void setNativeMethods()
int requestToSendSupport(int deviceId)
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 close(int deviceId)
bool stopReadThread(int deviceId)
static QHash< QSerialPortPrivate *, jlong > s_ptrToToken
int dataTerminalReadySupport(int deviceId)
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