4#include <QtCore/QJniEnvironment>
5#include <QtCore/QJniObject>
6#include <QtCore/QMutex>
7#include <QtCore/QPointer>
8#include <QtCore/QRandomGenerator>
9#include <QtCore/QReadWriteLock>
10#include <QtCore/QThread>
40 qCWarning(AndroidSerialLog) <<
"registerPointer called with null pointer";
54 token =
static_cast<jlong
>(QRandomGenerator::global()->generate64());
90 if (!serialPortPrivate) {
94 return qobject_cast<QSerialPort*>(serialPortPrivate->q_ptr);
97template <
typename Functor>
101 qCWarning(AndroidSerialLog) << context <<
": null serial port";
105 QThread*
const targetThread = serialPort->thread();
106 const bool sameThread = (targetThread == QThread::currentThread());
107 const bool hasEventLoop = targetThread && targetThread->eventDispatcher();
110 std::forward<Functor>(func)();
118 const bool ok = QMetaObject::invokeMethod(serialPort, std::forward<Functor>(func), Qt::BlockingQueuedConnection);
120 qCWarning(AndroidSerialLog) << context <<
": failed to invoke method on target thread";
125 qCWarning(AndroidSerialLog) << context <<
": target thread has no event loop, running inline fallback";
126 std::forward<Functor>(func)();
178 const MethodDef defs[] = {
207 for (
const auto& def : defs) {
208 *def.target = env->GetStaticMethodID(javaClass, def.name, def.sig);
210 qCWarning(AndroidSerialLog) <<
"Failed to cache method:" << def.name << def.sig;
211 (void)QJniEnvironment::checkAndClearExceptions(env);
217 qCDebug(AndroidSerialLog) <<
"All JNI method IDs cached successfully";
234 if (!env.isValid()) {
235 qCWarning(AndroidSerialLog) <<
"Invalid QJniEnvironment";
241 if (!resolvedClass) {
247 if (env->GetObjectRefType(resolvedClass) == JNILocalRefType) {
248 env->DeleteLocalRef(resolvedClass);
253 (void)env.checkAndClearExceptions();
259 qCWarning(AndroidSerialLog) <<
"Failed to cache JNI method IDs";
263 (void)env.checkAndClearExceptions();
268 (void)env.checkAndClearExceptions();
290static void jniDeviceNewData(JNIEnv* env, jobject obj, jlong token, jbyteArray data);
291static void jniDeviceException(JNIEnv* env, jobject obj, jlong token, jstring message);
295 qCDebug(AndroidSerialLog) <<
"Registering Native Functions";
297 const JNINativeMethod javaMethods[]{
299 {
"nativeDeviceNewData",
"(J[B)V",
reinterpret_cast<void*
>(
jniDeviceNewData)},
300 {
"nativeDeviceException",
"(JLjava/lang/String;)V",
reinterpret_cast<void*
>(
jniDeviceException)},
310 qCWarning(AndroidSerialLog) <<
"Failed to cache JNI method IDs";
314 qCDebug(AndroidSerialLog) <<
"Native Functions Registered Successfully";
324 qCWarning(AndroidSerialLog) <<
"nativeDeviceHasDisconnected called with token=0";
328 QPointer<QSerialPort> serialPort;
333 qCWarning(AndroidSerialLog) <<
"nativeDeviceHasDisconnected: stale token, object already destroyed";
336 qCDebug(AndroidSerialLog) <<
"Device disconnected:" << serialPort->portName();
342 QSerialPortPrivate* const p = lookupByToken(token);
344 qCDebug(AndroidSerialLog) <<
"Token already invalidated in nativeDeviceHasDisconnected";
348 QSerialPort*
const port = qobject_cast<QSerialPort*>(p->q_ptr);
349 if (port && port->isOpen()) {
351 qCDebug(AndroidSerialLog) <<
"Serial port closed in nativeDeviceHasDisconnected";
353 qCDebug(AndroidSerialLog) <<
"Serial port was already closed in nativeDeviceHasDisconnected";
356 "nativeDeviceHasDisconnected")) {
357 qCWarning(AndroidSerialLog) <<
"nativeDeviceHasDisconnected: failed to dispatch cleanup";
363 constexpr jsize kMaxNativePayloadBytes =
static_cast<jsize
>(
MAX_READ_SIZE);
366 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData called with token=0";
371 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData called with null data";
375 const jsize len = env->GetArrayLength(data);
377 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData received empty data array";
381 const jsize cappedLen = (len > kMaxNativePayloadBytes) ? kMaxNativePayloadBytes : len;
382 if (cappedLen != len) {
383 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData payload exceeds limit, truncating from" << len <<
"to"
384 << cappedLen <<
"bytes";
387 QByteArray payload(cappedLen, Qt::Uninitialized);
388 env->GetByteArrayRegion(data, 0, cappedLen,
reinterpret_cast<jbyte*
>(payload.data()));
389 if (QJniEnvironment::checkAndClearExceptions(env)) {
390 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData failed to copy JNI byte array";
399 if (!serialPortPrivate) {
400 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData: stale token, object already destroyed";
404 serialPortPrivate->
newDataArrived(payload.constData(), payload.size());
411 qCWarning(AndroidSerialLog) <<
"nativeDeviceException called with token=0";
416 qCWarning(AndroidSerialLog) <<
"nativeDeviceException called with null message";
420 const QString exceptionMessage = QJniObject(message).toString();
422 QPointer<QSerialPort> serialPort;
427 qCWarning(AndroidSerialLog) <<
"nativeDeviceException: stale token, object already destroyed";
432 qCWarning(AndroidSerialLog) <<
"Exception from Java:" << exceptionMessage;
436 [token, exceptionMessage]() {
437 QSerialPortPrivate* const p = lookupByToken(token);
439 qCDebug(AndroidSerialLog) <<
"Token already invalidated in nativeDeviceException";
443 p->exceptionArrived(exceptionMessage);
445 "nativeDeviceException")) {
446 qCWarning(AndroidSerialLog) <<
"nativeDeviceException: failed to dispatch exception callback";
457 jclass cls =
nullptr;
463 if (!ctx.
env.isValid()) {
464 qCWarning(AndroidSerialLog) <<
"Invalid QJniEnvironment in" << caller;
470 qCWarning(AndroidSerialLog) <<
"getSerialManagerClass returned null in" << caller;
484 QList<QSerialPortInfo> serialPortInfoList;
488 return serialPortInfoList;
493 if (!objArray.
get()) {
494 qCDebug(AndroidSerialLog) <<
"availableDevicesInfo returned null";
495 (void)ctx.
env.checkAndClearExceptions();
496 return serialPortInfoList;
499 if (ctx.
env.checkAndClearExceptions()) {
500 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling availableDevicesInfo";
501 return serialPortInfoList;
504 const jsize count = ctx.
env->GetArrayLength(objArray.
get());
505 for (jsize i = 0; i < count; ++i) {
507 ctx.
env.jniEnv(),
static_cast<jstring
>(ctx.
env->GetObjectArrayElement(objArray.
get(), i)));
509 qCWarning(AndroidSerialLog) <<
"Null string at index" << i;
513 const QStringList strList = QJniObject(jstr.
get()).toString().split(QLatin1Char(
'\t'));
515 if (strList.size() < 6) {
516 qCWarning(AndroidSerialLog) <<
"Invalid device info at index" << i <<
":" << strList;
532 serialPortInfoList.append(info);
535 (void)ctx.
env.checkAndClearExceptions();
537 return serialPortInfoList;
546 const QJniObject name = QJniObject::fromString(portName);
547 if (!name.isValid()) {
548 qCWarning(AndroidSerialLog) <<
"Invalid QJniObject for portName in getDeviceId";
558 AndroidSerialLog(), result, name.object<jstring>())) {
562 return static_cast<int>(result);
573 AndroidSerialLog(), result,
static_cast<jint
>(deviceId))) {
577 return static_cast<int>(result);
587 qCWarning(AndroidSerialLog) <<
"open called with null serialPort";
593 qCWarning(AndroidSerialLog) <<
"open called with unregistered pointer — call registerPointer first";
597 const QJniObject name = QJniObject::fromString(portName);
598 if (!name.isValid()) {
599 qCWarning(AndroidSerialLog) <<
"Invalid QJniObject for portName in open";
609 name.object<jstring>(), token)) {
613 return static_cast<int>(deviceId);
622 jboolean result = JNI_FALSE;
624 result,
static_cast<jint
>(deviceId))) {
628 return (result == JNI_TRUE);
633 const QJniObject name = QJniObject::fromString(portName);
634 if (!name.isValid()) {
635 qCWarning(AndroidSerialLog) <<
"Invalid QJniObject for portName in isOpen";
643 jboolean result = JNI_FALSE;
645 AndroidSerialLog(), result, name.object<jstring>())) {
649 return (result == JNI_TRUE);
656QByteArray
read(
int deviceId,
int length,
int timeout)
663 ctx.
env.jniEnv(),
static_cast<jbyteArray
>(
665 static_cast<jint
>(length),
static_cast<jint
>(timeout))));
668 qCWarning(AndroidSerialLog) <<
"read method returned null";
669 (void)ctx.
env.checkAndClearExceptions();
673 if (ctx.
env.checkAndClearExceptions()) {
674 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling read";
678 const jsize len = ctx.
env->GetArrayLength(jarray.
get());
679 jbyte*
const bytes = ctx.
env->GetByteArrayElements(jarray.
get(),
nullptr);
681 qCWarning(AndroidSerialLog) <<
"Failed to get byte array elements in read";
685 const QByteArray data(
reinterpret_cast<char*
>(bytes), len);
686 ctx.
env->ReleaseByteArrayElements(jarray.
get(), bytes, JNI_ABORT);
691int write(
int deviceId,
const char* data,
int length,
int timeout,
bool async)
693 if (!data || length <= 0) {
694 qCWarning(AndroidSerialLog) <<
"Invalid data or length in write";
703 ctx.
env->NewByteArray(
static_cast<jsize
>(length)));
705 qCWarning(AndroidSerialLog) <<
"Failed to create jbyteArray in write";
709 ctx.
env->SetByteArrayRegion(jarray.
get(), 0,
static_cast<jsize
>(length),
reinterpret_cast<const jbyte*
>(data));
710 if (ctx.
env.checkAndClearExceptions()) {
711 qCWarning(AndroidSerialLog) <<
"Exception occurred while setting byte array region in write";
718 static_cast<jint
>(timeout));
721 static_cast<jint
>(length),
static_cast<jint
>(timeout));
724 if (ctx.
env.checkAndClearExceptions()) {
725 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling write/writeAsync";
729 return static_cast<int>(result);
736bool setParameters(
int deviceId,
int baudRate,
int dataBits,
int stopBits,
int parity)
742 jboolean result = JNI_FALSE;
744 AndroidSerialLog(), result,
static_cast<jint
>(deviceId),
745 static_cast<jint
>(baudRate),
static_cast<jint
>(dataBits),
746 static_cast<jint
>(stopBits),
static_cast<jint
>(parity))) {
750 return (result == JNI_TRUE);
763 jboolean result = JNI_FALSE;
765 static_cast<jint
>(deviceId))) {
769 return (result == JNI_TRUE);
778 const jboolean jSet = set ? JNI_TRUE : JNI_FALSE;
779 jboolean result = JNI_FALSE;
781 static_cast<jint
>(deviceId), jSet)) {
785 return (result == JNI_TRUE);
836 return QSerialPort::PinoutSignals();
840 static_cast<jint
>(deviceId))));
842 qCWarning(AndroidSerialLog) <<
"getControlLines returned null";
843 (void)ctx.
env.checkAndClearExceptions();
844 return QSerialPort::PinoutSignals();
847 if (ctx.
env.checkAndClearExceptions()) {
848 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling getControlLines";
849 return QSerialPort::PinoutSignals();
852 jint*
const ints = ctx.
env->GetIntArrayElements(jarray.
get(),
nullptr);
854 qCWarning(AndroidSerialLog) <<
"Failed to get int array elements in getControlLines";
855 return QSerialPort::PinoutSignals();
858 const jsize len = ctx.
env->GetArrayLength(jarray.
get());
859 QSerialPort::PinoutSignals data = QSerialPort::PinoutSignals();
861 for (jsize i = 0; i < len; ++i) {
882 qCWarning(AndroidSerialLog) <<
"Unknown ControlLine value:" << ints[i];
887 ctx.
env->ReleaseIntArrayElements(jarray.
get(), ints, JNI_ABORT);
888 (void)ctx.
env.checkAndClearExceptions();
905 AndroidSerialLog(), flowControl,
static_cast<jint
>(deviceId))) {
909 return static_cast<int>(flowControl);
918 jboolean result = JNI_FALSE;
920 AndroidSerialLog(), result,
static_cast<jint
>(deviceId),
921 static_cast<jint
>(flowControl))) {
925 return result == JNI_TRUE;
938 const jboolean jInput = input ? JNI_TRUE : JNI_FALSE;
939 const jboolean jOutput = output ? JNI_TRUE : JNI_FALSE;
941 jboolean result = JNI_FALSE;
943 AndroidSerialLog(), result,
static_cast<jint
>(deviceId), jInput,
948 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)
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)
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
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