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 writeAsync = nullptr;
199 jmethodID setParameters = nullptr;
200 jmethodID getCarrierDetect = nullptr;
201 jmethodID getClearToSend = nullptr;
202 jmethodID getDataSetReady = nullptr;
203 jmethodID getDataTerminalReady = nullptr;
204 jmethodID setDataTerminalReady = nullptr;
205 jmethodID getRingIndicator = nullptr;
206 jmethodID getRequestToSend = nullptr;
207 jmethodID setRequestToSend = nullptr;
208 jmethodID getControlLines = nullptr;
209 jmethodID getFlowControl = nullptr;
210 jmethodID setFlowControl = nullptr;
211 jmethodID purgeBuffers = nullptr;
212 jmethodID setBreak = nullptr;
213 jmethodID startIoManager = nullptr;
214 jmethodID stopIoManager = nullptr;
215 jmethodID ioManagerRunning = nullptr;
216};
217
219static bool s_methodsCached = false;
220static QMutex s_cacheLock;
221static jclass s_serialManagerClass = nullptr;
222
223static bool cacheMethodIds(JNIEnv* env, jclass javaClass)
224{
225 struct MethodDef
226 {
227 jmethodID* target;
228 const char* name;
229 const char* sig;
230 };
231
232 const MethodDef defs[] = {
233 {&s_methods.availableDevicesInfo, "availableDevicesInfo", "()[Ljava/lang/String;"},
234 {&s_methods.getDeviceId, "getDeviceId", "(Ljava/lang/String;)I"},
235 {&s_methods.getDeviceHandle, "getDeviceHandle", "(I)I"},
236 {&s_methods.open, "open", "(Ljava/lang/String;J)I"},
237 {&s_methods.close, "close", "(I)Z"},
238 {&s_methods.isDeviceNameOpen, "isDeviceNameOpen", "(Ljava/lang/String;)Z"},
239 {&s_methods.read, "read", "(III)[B"},
240 {&s_methods.write, "write", "(I[BII)I"},
241 {&s_methods.writeAsync, "writeAsync", "(I[BI)I"},
242 {&s_methods.setParameters, "setParameters", "(IIIII)Z"},
243 {&s_methods.getCarrierDetect, "getCarrierDetect", "(I)Z"},
244 {&s_methods.getClearToSend, "getClearToSend", "(I)Z"},
245 {&s_methods.getDataSetReady, "getDataSetReady", "(I)Z"},
246 {&s_methods.getDataTerminalReady, "getDataTerminalReady", "(I)Z"},
247 {&s_methods.setDataTerminalReady, "setDataTerminalReady", "(IZ)Z"},
248 {&s_methods.getRingIndicator, "getRingIndicator", "(I)Z"},
249 {&s_methods.getRequestToSend, "getRequestToSend", "(I)Z"},
250 {&s_methods.setRequestToSend, "setRequestToSend", "(IZ)Z"},
251 {&s_methods.getControlLines, "getControlLines", "(I)[I"},
252 {&s_methods.getFlowControl, "getFlowControl", "(I)I"},
253 {&s_methods.setFlowControl, "setFlowControl", "(II)Z"},
254 {&s_methods.purgeBuffers, "purgeBuffers", "(IZZ)Z"},
255 {&s_methods.setBreak, "setBreak", "(IZ)Z"},
256 {&s_methods.startIoManager, "startIoManager", "(I)Z"},
257 {&s_methods.stopIoManager, "stopIoManager", "(I)Z"},
258 {&s_methods.ioManagerRunning, "ioManagerRunning", "(I)Z"},
259 };
260
261 for (const auto& def : defs) {
262 *def.target = env->GetStaticMethodID(javaClass, def.name, def.sig);
263 if (!*def.target) {
264 qCWarning(AndroidSerialLog) << "Failed to cache method:" << def.name << def.sig;
265 (void)QJniEnvironment::checkAndClearExceptions(env);
266 return false;
267 }
268 }
269
270 s_methodsCached = true;
271 qCDebug(AndroidSerialLog) << "All JNI method IDs cached successfully";
272 return true;
273}
274
275// ----------------------------------------------------------------------------
276// Class resolution
277// ----------------------------------------------------------------------------
278
280{
281 QMutexLocker locker(&s_cacheLock);
282
285 }
286
287 QJniEnvironment env;
288 if (!env.isValid()) {
289 qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment";
290 return nullptr;
291 }
292
294 const jclass resolvedClass = env.findClass(kJniUsbSerialManagerClassName);
295 if (!resolvedClass) {
296 qCWarning(AndroidSerialLog) << "Class Not Found:" << kJniUsbSerialManagerClassName;
297 return nullptr;
298 }
299
300 s_serialManagerClass = static_cast<jclass>(env->NewGlobalRef(resolvedClass));
301 if (env->GetObjectRefType(resolvedClass) == JNILocalRefType) {
302 env->DeleteLocalRef(resolvedClass);
303 }
304
306 qCWarning(AndroidSerialLog) << "Failed to create global ref for class:" << kJniUsbSerialManagerClassName;
307 (void)env.checkAndClearExceptions();
308 return nullptr;
309 }
310 }
311
312 if (!s_methodsCached && !cacheMethodIds(env.jniEnv(), s_serialManagerClass)) {
313 qCWarning(AndroidSerialLog) << "Failed to cache JNI method IDs";
314 env->DeleteGlobalRef(s_serialManagerClass);
315 s_serialManagerClass = nullptr;
316 s_methods = {};
317 (void)env.checkAndClearExceptions();
318 return nullptr;
319 }
320
321 s_methodsCached = true;
322 (void)env.checkAndClearExceptions();
324}
325
327{
328 QMutexLocker locker(&s_cacheLock);
329 QJniEnvironment env;
330 if (s_serialManagerClass && env.isValid()) {
331 env->DeleteGlobalRef(s_serialManagerClass);
332 }
333 s_serialManagerClass = nullptr;
334 s_methods = {};
335 s_methodsCached = false;
336}
337
338// ----------------------------------------------------------------------------
339// Native method registration
340// ----------------------------------------------------------------------------
341
342// Forward declarations for JNI callbacks (defined below)
343static void jniDeviceHasDisconnected(JNIEnv* env, jobject obj, jlong token);
344static void jniDeviceNewData(JNIEnv* env, jobject obj, jlong token, jbyteArray data);
345static void jniDeviceException(JNIEnv* env, jobject obj, jlong token, jstring message);
346
348{
349 qCDebug(AndroidSerialLog) << "Registering Native Functions";
350
351 const JNINativeMethod javaMethods[]{
352 {"nativeDeviceHasDisconnected", "(J)V", reinterpret_cast<void*>(jniDeviceHasDisconnected)},
353 {"nativeDeviceNewData", "(J[B)V", reinterpret_cast<void*>(jniDeviceNewData)},
354 {"nativeDeviceException", "(JLjava/lang/String;)V", reinterpret_cast<void*>(jniDeviceException)},
355 };
356
357 QJniEnvironment env;
358 if (!env.registerNativeMethods(kJniUsbSerialManagerClassName, javaMethods, std::size(javaMethods))) {
359 qCWarning(AndroidSerialLog) << "Failed to register native methods for" << kJniUsbSerialManagerClassName;
360 return;
361 }
362
363 if (!getSerialManagerClass()) {
364 qCWarning(AndroidSerialLog) << "Failed to cache JNI method IDs";
365 return;
366 }
367
368 qCDebug(AndroidSerialLog) << "Native Functions Registered Successfully";
369}
370
371// ----------------------------------------------------------------------------
372// JNI callbacks (called from Java threads)
373// ----------------------------------------------------------------------------
374
375static void jniDeviceHasDisconnected(JNIEnv*, jobject, jlong token)
376{
377 if (token == 0) {
378 qCWarning(AndroidSerialLog) << "nativeDeviceHasDisconnected called with token=0";
379 return;
380 }
381
382 QPointer<QSerialPort> serialPort;
383 {
384 QReadLocker locker(&s_ptrLock);
385 serialPort = lookupPortByTokenLocked(token);
386 if (!serialPort) {
387 qCWarning(AndroidSerialLog) << "nativeDeviceHasDisconnected: stale token, object already destroyed";
388 return;
389 }
390 qCDebug(AndroidSerialLog) << "Device disconnected:" << serialPort->portName();
391 }
392
394 serialPort.data(),
395 [token]() {
396 QSerialPortPrivate* const p = lookupByToken(token);
397 if (!p) {
398 qCDebug(AndroidSerialLog) << "Token already invalidated in nativeDeviceHasDisconnected";
399 return;
400 }
401
402 QSerialPort* const port = qobject_cast<QSerialPort*>(p->q_ptr);
403 if (port && port->isOpen()) {
404 port->close();
405 qCDebug(AndroidSerialLog) << "Serial port closed in nativeDeviceHasDisconnected";
406 } else {
407 qCDebug(AndroidSerialLog) << "Serial port was already closed in nativeDeviceHasDisconnected";
408 }
409 },
410 "nativeDeviceHasDisconnected")) {
411 qCWarning(AndroidSerialLog) << "nativeDeviceHasDisconnected: failed to dispatch cleanup";
412 }
413}
414
415static void jniDeviceNewData(JNIEnv* env, jobject, jlong token, jbyteArray data)
416{
417 constexpr jsize kMaxNativePayloadBytes = static_cast<jsize>(MAX_READ_SIZE);
418
419 if (token == 0) {
420 qCWarning(AndroidSerialLog) << "nativeDeviceNewData called with token=0";
421 return;
422 }
423
424 if (!data) {
425 qCWarning(AndroidSerialLog) << "nativeDeviceNewData called with null data";
426 return;
427 }
428
429 const jsize len = env->GetArrayLength(data);
430 if (len <= 0) {
431 qCWarning(AndroidSerialLog) << "nativeDeviceNewData received empty data array";
432 return;
433 }
434
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";
439 }
440
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";
445 return;
446 }
447
448 {
449 // Deliver inline while holding read lock so unregister/destroy cannot
450 // invalidate the pointer until this handoff is complete.
451 QReadLocker locker(&s_ptrLock);
452 QSerialPortPrivate* const serialPortPrivate = s_tokenToPtr.value(token, nullptr);
453 if (!serialPortPrivate) {
454 qCWarning(AndroidSerialLog) << "nativeDeviceNewData: stale token, object already destroyed";
455 return;
456 }
457
458 serialPortPrivate->newDataArrived(payload.constData(), payload.size());
459 }
460}
461
462static void jniDeviceException(JNIEnv*, jobject, jlong token, jstring message)
463{
464 if (token == 0) {
465 qCWarning(AndroidSerialLog) << "nativeDeviceException called with token=0";
466 return;
467 }
468
469 if (!message) {
470 qCWarning(AndroidSerialLog) << "nativeDeviceException called with null message";
471 return;
472 }
473
474 const QString exceptionMessage = QJniObject(message).toString();
475
476 QPointer<QSerialPort> serialPort;
477 {
478 QReadLocker locker(&s_ptrLock);
479 serialPort = lookupPortByTokenLocked(token);
480 if (!serialPort) {
481 qCWarning(AndroidSerialLog) << "nativeDeviceException: stale token, object already destroyed";
482 return;
483 }
484 }
485
486 qCWarning(AndroidSerialLog) << "Exception from Java:" << exceptionMessage;
487
489 serialPort.data(),
490 [token, exceptionMessage]() {
491 QSerialPortPrivate* const p = lookupByToken(token);
492 if (!p) {
493 qCDebug(AndroidSerialLog) << "Token already invalidated in nativeDeviceException";
494 return;
495 }
496
497 p->exceptionArrived(exceptionMessage);
498 },
499 "nativeDeviceException")) {
500 qCWarning(AndroidSerialLog) << "nativeDeviceException: failed to dispatch exception callback";
501 }
502}
503
504// ----------------------------------------------------------------------------
505// Helper: get env + class + check cached method in one shot
506// ----------------------------------------------------------------------------
507
509{
510 QJniEnvironment env;
511 jclass cls = nullptr;
512 bool valid = false;
513};
514
515static bool getContext(JniContext& ctx, const char* caller)
516{
517 if (!ctx.env.isValid()) {
518 qCWarning(AndroidSerialLog) << "Invalid QJniEnvironment in" << caller;
519 return false;
520 }
521
523 if (!ctx.cls) {
524 qCWarning(AndroidSerialLog) << "getSerialManagerClass returned null in" << caller;
525 return false;
526 }
527
528 ctx.valid = true;
529 return true;
530}
531
532// ----------------------------------------------------------------------------
533// Device enumeration
534// ----------------------------------------------------------------------------
535
536QList<QSerialPortInfo> availableDevices()
537{
538 QList<QSerialPortInfo> serialPortInfoList;
539
540 JniContext ctx;
541 if (!getContext(ctx, "availableDevices"))
542 return serialPortInfoList;
543
545 ctx.env.jniEnv(),
546 static_cast<jobjectArray>(ctx.env->CallStaticObjectMethod(ctx.cls, s_methods.availableDevicesInfo)));
547 if (!objArray.get()) {
548 qCDebug(AndroidSerialLog) << "availableDevicesInfo returned null";
549 (void)ctx.env.checkAndClearExceptions();
550 return serialPortInfoList;
551 }
552
553 if (ctx.env.checkAndClearExceptions()) {
554 qCWarning(AndroidSerialLog) << "Exception occurred while calling availableDevicesInfo";
555 return serialPortInfoList;
556 }
557
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)));
562 if (!jstr.get()) {
563 qCWarning(AndroidSerialLog) << "Null string at index" << i;
564 continue;
565 }
566
567 const QStringList strList = QJniObject(jstr.get()).toString().split(QLatin1Char('\t'));
568
569 if (strList.size() < 6) {
570 qCWarning(AndroidSerialLog) << "Invalid device info at index" << i << ":" << strList;
571 continue;
572 }
573
574 bool pidOK, vidOK;
577 info.device = strList[0];
578 info.description = strList[1];
579 info.manufacturer = strList[2];
580 info.serialNumber = strList[3];
581 info.productIdentifier = strList[4].toInt(&pidOK);
582 info.hasProductIdentifier = (pidOK && (info.productIdentifier != INVALID_DEVICE_ID));
583 info.vendorIdentifier = strList[5].toInt(&vidOK);
584 info.hasVendorIdentifier = (vidOK && (info.vendorIdentifier != INVALID_DEVICE_ID));
585
586 serialPortInfoList.append(info);
587 }
588
589 (void)ctx.env.checkAndClearExceptions();
590
591 return serialPortInfoList;
592}
593
594// ----------------------------------------------------------------------------
595// Device ID / handle lookup
596// ----------------------------------------------------------------------------
597
598int getDeviceId(const QString& portName)
599{
600 const QJniObject name = QJniObject::fromString(portName);
601 if (!name.isValid()) {
602 qCWarning(AndroidSerialLog) << "Invalid QJniObject for portName in getDeviceId";
603 return -1;
604 }
605
606 JniContext ctx;
607 if (!getContext(ctx, "getDeviceId"))
608 return -1;
609
610 jint result = -1;
612 AndroidSerialLog(), result, name.object<jstring>())) {
613 return -1;
614 }
615
616 return static_cast<int>(result);
617}
618
619int getDeviceHandle(int deviceId)
620{
621 JniContext ctx;
622 if (!getContext(ctx, "getDeviceHandle"))
623 return -1;
624
625 jint result = -1;
627 AndroidSerialLog(), result, static_cast<jint>(deviceId))) {
628 return -1;
629 }
630
631 return static_cast<int>(result);
632}
633
634// ----------------------------------------------------------------------------
635// Open / close / isOpen
636// ----------------------------------------------------------------------------
637
638int open(const QString& portName, QSerialPortPrivate* classPtr)
639{
640 if (!classPtr) {
641 qCWarning(AndroidSerialLog) << "open called with null serialPort";
642 return INVALID_DEVICE_ID;
643 }
644
645 const jlong token = lookupToken(classPtr);
646 if (token == 0) {
647 qCWarning(AndroidSerialLog) << "open called with unregistered pointer — call registerPointer first";
648 return INVALID_DEVICE_ID;
649 }
650
651 const QJniObject name = QJniObject::fromString(portName);
652 if (!name.isValid()) {
653 qCWarning(AndroidSerialLog) << "Invalid QJniObject for portName in open";
654 return INVALID_DEVICE_ID;
655 }
656
657 JniContext ctx;
658 if (!getContext(ctx, "open"))
659 return INVALID_DEVICE_ID;
660
661 jint deviceId = INVALID_DEVICE_ID;
662 if (!AndroidInterface::callStaticIntMethod(ctx.env, ctx.cls, s_methods.open, "open", AndroidSerialLog(), deviceId,
663 name.object<jstring>(), token)) {
664 return INVALID_DEVICE_ID;
665 }
666
667 return static_cast<int>(deviceId);
668}
669
670bool close(int deviceId)
671{
672 JniContext ctx;
673 if (!getContext(ctx, "close"))
674 return false;
675
676 jboolean result = JNI_FALSE;
677 if (!AndroidInterface::callStaticBooleanMethod(ctx.env, ctx.cls, s_methods.close, "close", AndroidSerialLog(),
678 result, static_cast<jint>(deviceId))) {
679 return false;
680 }
681
682 return (result == JNI_TRUE);
683}
684
685bool isOpen(const QString& portName)
686{
687 const QJniObject name = QJniObject::fromString(portName);
688 if (!name.isValid()) {
689 qCWarning(AndroidSerialLog) << "Invalid QJniObject for portName in isOpen";
690 return false;
691 }
692
693 JniContext ctx;
694 if (!getContext(ctx, "isOpen"))
695 return false;
696
697 jboolean result = JNI_FALSE;
699 AndroidSerialLog(), result, name.object<jstring>())) {
700 return false;
701 }
702
703 return (result == JNI_TRUE);
704}
705
706// ----------------------------------------------------------------------------
707// Read / write
708// ----------------------------------------------------------------------------
709
710QByteArray read(int deviceId, int length, int timeout)
711{
712 JniContext ctx;
713 if (!getContext(ctx, "read"))
714 return QByteArray();
715
717 ctx.env.jniEnv(), static_cast<jbyteArray>(
718 ctx.env->CallStaticObjectMethod(ctx.cls, s_methods.read, static_cast<jint>(deviceId),
719 static_cast<jint>(length), static_cast<jint>(timeout))));
720
721 if (!jarray.get()) {
722 qCWarning(AndroidSerialLog) << "read method returned null";
723 (void)ctx.env.checkAndClearExceptions();
724 return QByteArray();
725 }
726
727 if (ctx.env.checkAndClearExceptions()) {
728 qCWarning(AndroidSerialLog) << "Exception occurred while calling read";
729 return QByteArray();
730 }
731
732 const jsize len = ctx.env->GetArrayLength(jarray.get());
733 jbyte* const bytes = ctx.env->GetByteArrayElements(jarray.get(), nullptr);
734 if (!bytes) {
735 qCWarning(AndroidSerialLog) << "Failed to get byte array elements in read";
736 return QByteArray();
737 }
738
739 const QByteArray data(reinterpret_cast<char*>(bytes), len);
740 ctx.env->ReleaseByteArrayElements(jarray.get(), bytes, JNI_ABORT);
741
742 return data;
743}
744
745int write(int deviceId, const char* data, int length, int timeout, bool async)
746{
747 if (!data || length <= 0) {
748 qCWarning(AndroidSerialLog) << "Invalid data or length in write";
749 return -1;
750 }
751
752 JniContext ctx;
753 if (!getContext(ctx, "write"))
754 return -1;
755
757 ctx.env->NewByteArray(static_cast<jsize>(length)));
758 if (!jarray.get()) {
759 qCWarning(AndroidSerialLog) << "Failed to create jbyteArray in write";
760 return -1;
761 }
762
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";
766 return -1;
767 }
768
769 jint result;
770 if (async) {
771 result = ctx.env->CallStaticIntMethod(ctx.cls, s_methods.writeAsync, static_cast<jint>(deviceId), jarray.get(),
772 static_cast<jint>(timeout));
773 } else {
774 result = ctx.env->CallStaticIntMethod(ctx.cls, s_methods.write, static_cast<jint>(deviceId), jarray.get(),
775 static_cast<jint>(length), static_cast<jint>(timeout));
776 }
777
778 if (ctx.env.checkAndClearExceptions()) {
779 qCWarning(AndroidSerialLog) << "Exception occurred while calling write/writeAsync";
780 return -1;
781 }
782
783 return static_cast<int>(result);
784}
785
786// ----------------------------------------------------------------------------
787// Port configuration
788// ----------------------------------------------------------------------------
789
790bool setParameters(int deviceId, int baudRate, int dataBits, int stopBits, int parity)
791{
792 JniContext ctx;
793 if (!getContext(ctx, "setParameters"))
794 return false;
795
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))) {
801 return false;
802 }
803
804 return (result == JNI_TRUE);
805}
806
807// ----------------------------------------------------------------------------
808// Control line helpers (DRY macro for bool getters)
809// ----------------------------------------------------------------------------
810
811static bool callBoolMethod(jmethodID method, int deviceId, const char* name)
812{
813 JniContext ctx;
814 if (!getContext(ctx, name))
815 return false;
816
817 jboolean result = JNI_FALSE;
818 if (!AndroidInterface::callStaticBooleanMethod(ctx.env, ctx.cls, method, name, AndroidSerialLog(), result,
819 static_cast<jint>(deviceId))) {
820 return false;
821 }
822
823 return (result == JNI_TRUE);
824}
825
826static bool callBoolSetMethod(jmethodID method, int deviceId, bool set, const char* name)
827{
828 JniContext ctx;
829 if (!getContext(ctx, name))
830 return false;
831
832 const jboolean jSet = set ? JNI_TRUE : JNI_FALSE;
833 jboolean result = JNI_FALSE;
834 if (!AndroidInterface::callStaticBooleanMethod(ctx.env, ctx.cls, method, name, AndroidSerialLog(), result,
835 static_cast<jint>(deviceId), jSet)) {
836 return false;
837 }
838
839 return (result == JNI_TRUE);
840}
841
842// ----------------------------------------------------------------------------
843// Control lines
844// ----------------------------------------------------------------------------
845
846bool getCarrierDetect(int deviceId)
847{
848 return callBoolMethod(s_methods.getCarrierDetect, deviceId, "getCarrierDetect");
849}
850
851bool getClearToSend(int deviceId)
852{
853 return callBoolMethod(s_methods.getClearToSend, deviceId, "getClearToSend");
854}
855
856bool getDataSetReady(int deviceId)
857{
858 return callBoolMethod(s_methods.getDataSetReady, deviceId, "getDataSetReady");
859}
860
861bool getDataTerminalReady(int deviceId)
862{
863 return callBoolMethod(s_methods.getDataTerminalReady, deviceId, "getDataTerminalReady");
864}
865
866bool getRingIndicator(int deviceId)
867{
868 return callBoolMethod(s_methods.getRingIndicator, deviceId, "getRingIndicator");
869}
870
871bool getRequestToSend(int deviceId)
872{
873 return callBoolMethod(s_methods.getRequestToSend, deviceId, "getRequestToSend");
874}
875
876bool setDataTerminalReady(int deviceId, bool set)
877{
878 return callBoolSetMethod(s_methods.setDataTerminalReady, deviceId, set, "setDataTerminalReady");
879}
880
881bool setRequestToSend(int deviceId, bool set)
882{
883 return callBoolSetMethod(s_methods.setRequestToSend, deviceId, set, "setRequestToSend");
884}
885
886QSerialPort::PinoutSignals getControlLines(int deviceId)
887{
888 JniContext ctx;
889 if (!getContext(ctx, "getControlLines"))
890 return QSerialPort::PinoutSignals();
891
893 ctx.env.jniEnv(), static_cast<jintArray>(ctx.env->CallStaticObjectMethod(ctx.cls, s_methods.getControlLines,
894 static_cast<jint>(deviceId))));
895 if (!jarray.get()) {
896 qCWarning(AndroidSerialLog) << "getControlLines returned null";
897 (void)ctx.env.checkAndClearExceptions();
898 return QSerialPort::PinoutSignals();
899 }
900
901 if (ctx.env.checkAndClearExceptions()) {
902 qCWarning(AndroidSerialLog) << "Exception occurred while calling getControlLines";
903 return QSerialPort::PinoutSignals();
904 }
905
906 jint* const ints = ctx.env->GetIntArrayElements(jarray.get(), nullptr);
907 if (!ints) {
908 qCWarning(AndroidSerialLog) << "Failed to get int array elements in getControlLines";
909 return QSerialPort::PinoutSignals();
910 }
911
912 const jsize len = ctx.env->GetArrayLength(jarray.get());
913 QSerialPort::PinoutSignals data = QSerialPort::PinoutSignals();
914
915 for (jsize i = 0; i < len; ++i) {
916 switch (ints[i]) {
917 case RtsControlLine:
919 break;
920 case CtsControlLine:
922 break;
923 case DtrControlLine:
925 break;
926 case DsrControlLine:
928 break;
929 case CdControlLine:
931 break;
932 case RiControlLine:
934 break;
935 default:
936 qCWarning(AndroidSerialLog) << "Unknown ControlLine value:" << ints[i];
937 break;
938 }
939 }
940
941 ctx.env->ReleaseIntArrayElements(jarray.get(), ints, JNI_ABORT);
942 (void)ctx.env.checkAndClearExceptions();
943
944 return data;
945}
946
947// ----------------------------------------------------------------------------
948// Flow control
949// ----------------------------------------------------------------------------
950
951int getFlowControl(int deviceId)
952{
953 JniContext ctx;
954 if (!getContext(ctx, "getFlowControl"))
956
957 jint flowControl = QSerialPort::NoFlowControl;
959 AndroidSerialLog(), flowControl, static_cast<jint>(deviceId))) {
961 }
962
963 return static_cast<int>(flowControl);
964}
965
966bool setFlowControl(int deviceId, int flowControl)
967{
968 JniContext ctx;
969 if (!getContext(ctx, "setFlowControl"))
970 return false;
971
972 jboolean result = JNI_FALSE;
974 AndroidSerialLog(), result, static_cast<jint>(deviceId),
975 static_cast<jint>(flowControl))) {
976 return false;
977 }
978
979 return result == JNI_TRUE;
980}
981
982// ----------------------------------------------------------------------------
983// Buffer / break
984// ----------------------------------------------------------------------------
985
986bool purgeBuffers(int deviceId, bool input, bool output)
987{
988 JniContext ctx;
989 if (!getContext(ctx, "purgeBuffers"))
990 return false;
991
992 const jboolean jInput = input ? JNI_TRUE : JNI_FALSE;
993 const jboolean jOutput = output ? JNI_TRUE : JNI_FALSE;
994
995 jboolean result = JNI_FALSE;
997 AndroidSerialLog(), result, static_cast<jint>(deviceId), jInput,
998 jOutput)) {
999 return false;
1000 }
1001
1002 return (result == JNI_TRUE);
1003}
1004
1005bool setBreak(int deviceId, bool set)
1006{
1007 return callBoolSetMethod(s_methods.setBreak, deviceId, set, "setBreak");
1008}
1009
1010// ----------------------------------------------------------------------------
1011// IO manager (read thread)
1012// ----------------------------------------------------------------------------
1013
1014bool startReadThread(int deviceId)
1015{
1016 return callBoolMethod(s_methods.startIoManager, deviceId, "startIoManager");
1017}
1018
1019bool stopReadThread(int deviceId)
1020{
1021 return callBoolMethod(s_methods.stopIoManager, deviceId, "stopIoManager");
1022}
1023
1024bool readThreadRunning(int deviceId)
1025{
1026 return callBoolMethod(s_methods.ioManagerRunning, deviceId, "ioManagerRunning");
1027}
1028
1029} // 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
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()
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
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