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)();
235 const MethodDef defs[] = {
267 for (
const auto& def : defs) {
268 *def.target = env->GetStaticMethodID(javaClass, def.name, def.sig);
270 qCWarning(AndroidSerialLog) <<
"Failed to cache method:" << def.name << def.sig;
271 (void)QJniEnvironment::checkAndClearExceptions(env);
277 qCDebug(AndroidSerialLog) <<
"All JNI method IDs cached successfully";
294 if (!env.isValid()) {
295 qCWarning(AndroidSerialLog) <<
"Invalid QJniEnvironment";
301 if (!resolvedClass) {
307 if (env->GetObjectRefType(resolvedClass) == JNILocalRefType) {
308 env->DeleteLocalRef(resolvedClass);
313 (void)env.checkAndClearExceptions();
319 qCWarning(AndroidSerialLog) <<
"Failed to cache JNI method IDs";
323 (void)env.checkAndClearExceptions();
328 (void)env.checkAndClearExceptions();
350static void jniDeviceNewData(JNIEnv* env, jobject obj, jlong token, jbyteArray data);
351static void jniDeviceException(JNIEnv* env, jobject obj, jlong token, jstring message);
355 qCDebug(AndroidSerialLog) <<
"Registering Native Functions";
357 const JNINativeMethod javaMethods[]{
359 {
"nativeDeviceNewData",
"(J[B)V",
reinterpret_cast<void*
>(
jniDeviceNewData)},
360 {
"nativeDeviceException",
"(JLjava/lang/String;)V",
reinterpret_cast<void*
>(
jniDeviceException)},
370 qCWarning(AndroidSerialLog) <<
"Failed to cache JNI method IDs";
374 qCDebug(AndroidSerialLog) <<
"Native Functions Registered Successfully";
384 qCWarning(AndroidSerialLog) <<
"nativeDeviceHasDisconnected called with token=0";
388 QPointer<QSerialPort> serialPort;
393 qCWarning(AndroidSerialLog) <<
"nativeDeviceHasDisconnected: stale token, object already destroyed";
396 qCDebug(AndroidSerialLog) <<
"Device disconnected:" << serialPort->portName();
402 QSerialPortPrivate* const p = lookupByToken(token);
404 qCDebug(AndroidSerialLog) <<
"Token already invalidated in nativeDeviceHasDisconnected";
408 QSerialPort*
const port = qobject_cast<QSerialPort*>(p->q_ptr);
409 if (port && port->isOpen()) {
411 qCDebug(AndroidSerialLog) <<
"Serial port closed in nativeDeviceHasDisconnected";
413 qCDebug(AndroidSerialLog) <<
"Serial port was already closed in nativeDeviceHasDisconnected";
416 "nativeDeviceHasDisconnected")) {
417 qCWarning(AndroidSerialLog) <<
"nativeDeviceHasDisconnected: failed to dispatch cleanup";
423 constexpr jsize kMaxNativePayloadBytes =
static_cast<jsize
>(
MAX_READ_SIZE);
426 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData called with token=0";
431 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData called with null data";
435 const jsize len = env->GetArrayLength(data);
437 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData received empty data array";
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";
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";
459 if (!serialPortPrivate) {
460 qCWarning(AndroidSerialLog) <<
"nativeDeviceNewData: stale token, object already destroyed";
464 serialPortPrivate->
newDataArrived(payload.constData(), payload.size());
471 qCWarning(AndroidSerialLog) <<
"nativeDeviceException called with token=0";
476 qCWarning(AndroidSerialLog) <<
"nativeDeviceException called with null message";
480 const QString exceptionMessage = QJniObject(message).toString();
482 QPointer<QSerialPort> serialPort;
487 qCWarning(AndroidSerialLog) <<
"nativeDeviceException: stale token, object already destroyed";
492 qCWarning(AndroidSerialLog) <<
"Exception from Java:" << exceptionMessage;
496 [token, exceptionMessage]() {
497 QSerialPortPrivate* const p = lookupByToken(token);
499 qCDebug(AndroidSerialLog) <<
"Token already invalidated in nativeDeviceException";
503 p->exceptionArrived(exceptionMessage);
505 "nativeDeviceException")) {
506 qCWarning(AndroidSerialLog) <<
"nativeDeviceException: failed to dispatch exception callback";
517 jclass cls =
nullptr;
523 if (!ctx.
env.isValid()) {
524 qCWarning(AndroidSerialLog) <<
"Invalid QJniEnvironment in" << caller;
530 qCWarning(AndroidSerialLog) <<
"getSerialManagerClass returned null in" << caller;
544 QList<QSerialPortInfo> serialPortInfoList;
548 return serialPortInfoList;
553 if (!objArray.
get()) {
554 qCDebug(AndroidSerialLog) <<
"availableDevicesInfo returned null";
555 (void)ctx.
env.checkAndClearExceptions();
556 return serialPortInfoList;
559 if (ctx.
env.checkAndClearExceptions()) {
560 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling availableDevicesInfo";
561 return serialPortInfoList;
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)));
569 qCWarning(AndroidSerialLog) <<
"Null string at index" << i;
573 const QStringList strList = QJniObject(jstr.
get()).toString().split(QLatin1Char(
'\t'));
575 if (strList.size() < 6) {
576 qCWarning(AndroidSerialLog) <<
"Invalid device info at index" << i <<
":" << strList;
592 serialPortInfoList.append(info);
595 (void)ctx.
env.checkAndClearExceptions();
597 return serialPortInfoList;
606 const QJniObject name = QJniObject::fromString(portName);
607 if (!name.isValid()) {
608 qCWarning(AndroidSerialLog) <<
"Invalid QJniObject for portName in getDeviceId";
618 AndroidSerialLog(), result, name.object<jstring>())) {
622 return static_cast<int>(result);
633 AndroidSerialLog(), result,
static_cast<jint
>(deviceId))) {
637 return static_cast<int>(result);
647 qCWarning(AndroidSerialLog) <<
"open called with null serialPort";
653 qCWarning(AndroidSerialLog) <<
"open called with unregistered pointer — call registerPointer first";
657 const QJniObject name = QJniObject::fromString(portName);
658 if (!name.isValid()) {
659 qCWarning(AndroidSerialLog) <<
"Invalid QJniObject for portName in open";
669 name.object<jstring>(), token)) {
673 return static_cast<int>(deviceId);
682 jboolean result = JNI_FALSE;
684 result,
static_cast<jint
>(deviceId))) {
688 return (result == JNI_TRUE);
693 const QJniObject name = QJniObject::fromString(portName);
694 if (!name.isValid()) {
695 qCWarning(AndroidSerialLog) <<
"Invalid QJniObject for portName in isOpen";
703 jboolean result = JNI_FALSE;
705 AndroidSerialLog(), result, name.object<jstring>())) {
709 return (result == JNI_TRUE);
716QByteArray
read(
int deviceId,
int length,
int timeout)
723 ctx.
env.jniEnv(),
static_cast<jbyteArray
>(
725 static_cast<jint
>(length),
static_cast<jint
>(timeout))));
728 qCWarning(AndroidSerialLog) <<
"read method returned null";
729 (void)ctx.
env.checkAndClearExceptions();
733 if (ctx.
env.checkAndClearExceptions()) {
734 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling read";
738 const jsize len = ctx.
env->GetArrayLength(jarray.
get());
739 jbyte*
const bytes = ctx.
env->GetByteArrayElements(jarray.
get(),
nullptr);
741 qCWarning(AndroidSerialLog) <<
"Failed to get byte array elements in read";
745 const QByteArray data(
reinterpret_cast<char*
>(bytes), len);
746 ctx.
env->ReleaseByteArrayElements(jarray.
get(), bytes, JNI_ABORT);
751int write(
int deviceId,
const char* data,
int length,
int timeout,
bool async)
753 if (!data || length <= 0) {
754 qCWarning(AndroidSerialLog) <<
"Invalid data or length in write";
763 ctx.
env->NewByteArray(
static_cast<jsize
>(length)));
765 qCWarning(AndroidSerialLog) <<
"Failed to create jbyteArray in write";
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";
778 static_cast<jint
>(timeout));
781 static_cast<jint
>(length),
static_cast<jint
>(timeout));
784 if (ctx.
env.checkAndClearExceptions()) {
785 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling write/writeAsync";
789 return static_cast<int>(result);
794 if (!data || length <= 0) {
795 qCWarning(AndroidSerialLog) <<
"Invalid data or length in writeWithProgress";
804 ctx.
env->NewByteArray(
static_cast<jsize
>(length)));
806 qCWarning(AndroidSerialLog) <<
"Failed to create jbyteArray in writeWithProgress";
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";
818 static_cast<jint
>(length),
static_cast<jint
>(timeout));
820 if (ctx.
env.checkAndClearExceptions()) {
821 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling writeWithProgress";
825 return static_cast<int>(result);
832bool setParameters(
int deviceId,
int baudRate,
int dataBits,
int stopBits,
int parity)
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))) {
846 return (result == JNI_TRUE);
859 jboolean result = JNI_FALSE;
861 static_cast<jint
>(deviceId))) {
865 return (result == JNI_TRUE);
874 const jboolean jSet = set ? JNI_TRUE : JNI_FALSE;
875 jboolean result = JNI_FALSE;
877 static_cast<jint
>(deviceId), jSet)) {
881 return (result == JNI_TRUE);
926 if (!
getContext(ctx,
"getDataTerminalReadySupport"))
930 "getDataTerminalReadySupport", AndroidSerialLog(), result, deviceId)) {
933 return static_cast<int>(result);
944 if (!
getContext(ctx,
"getRequestToSendSupport")) {
949 "getRequestToSendSupport", AndroidSerialLog(), result, deviceId)) {
952 return static_cast<int>(result);
959 return QSerialPort::PinoutSignals();
963 static_cast<jint
>(deviceId))));
965 qCWarning(AndroidSerialLog) <<
"getControlLines returned null";
966 (void)ctx.
env.checkAndClearExceptions();
967 return QSerialPort::PinoutSignals();
970 if (ctx.
env.checkAndClearExceptions()) {
971 qCWarning(AndroidSerialLog) <<
"Exception occurred while calling getControlLines";
972 return QSerialPort::PinoutSignals();
975 jint*
const ints = ctx.
env->GetIntArrayElements(jarray.
get(),
nullptr);
977 qCWarning(AndroidSerialLog) <<
"Failed to get int array elements in getControlLines";
978 return QSerialPort::PinoutSignals();
981 const jsize len = ctx.
env->GetArrayLength(jarray.
get());
982 QSerialPort::PinoutSignals data = QSerialPort::PinoutSignals();
984 for (jsize i = 0; i < len; ++i) {
1005 qCWarning(AndroidSerialLog) <<
"Unknown ControlLine value:" << ints[i];
1010 ctx.
env->ReleaseIntArrayElements(jarray.
get(), ints, JNI_ABORT);
1011 (void)ctx.
env.checkAndClearExceptions();
1028 AndroidSerialLog(), flowControl,
static_cast<jint
>(deviceId))) {
1032 return static_cast<int>(flowControl);
1041 jboolean result = JNI_FALSE;
1043 AndroidSerialLog(), result,
static_cast<jint
>(deviceId),
1044 static_cast<jint
>(flowControl))) {
1048 return result == JNI_TRUE;
1061 const jboolean jInput = input ? JNI_TRUE : JNI_FALSE;
1062 const jboolean jOutput = output ? JNI_TRUE : JNI_FALSE;
1064 jboolean result = JNI_FALSE;
1066 AndroidSerialLog(), result,
static_cast<jint
>(deviceId), jInput,
1071 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
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 setDataTerminalReady(int deviceId, bool set)
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 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
jmethodID getRequestToSend
jmethodID availableDevicesInfo
jmethodID requestToSendSupport
jmethodID ioManagerRunning
jmethodID writeWithProgress
jmethodID getDataSetReady
jmethodID setDataTerminalReady
jmethodID getCarrierDetect
jmethodID getDeviceHandle
jmethodID isDeviceNameOpen
jmethodID getDataTerminalReady
jmethodID getRingIndicator
jmethodID setRequestToSend
jmethodID getControlLines
jmethodID dataTerminalReadySupport