// Copyright (C) 2016 Lauri Laanmets (Proekspert AS) // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qbluetoothsocket.h" #include "qbluetoothsocket_android_p.h" #include "qbluetoothaddress.h" #include "qbluetoothdeviceinfo.h" #include "qbluetoothserviceinfo.h" #include "android/androidutils_p.h" #include "android/jni_android_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID) #define USE_FALLBACK true Q_BLUETOOTH_EXPORT bool useReverseUuidWorkAroundConnect = true; /* BluetoothSocket.connect() can block up to 10s. Therefore it must be * in a separate thread. Unfortunately if BluetoothSocket.close() is * called while connect() is still blocking the resulting behavior is not reliable. * This may well be an Android platform bug. In any case, close() must * be queued up until connect() has returned. * * The WorkerThread manages the connect() and close() calls. Interaction * with the main thread happens via signals and slots. There is an accepted but * undesirable side effect of this approach as the user may call connect() * and close() and the socket would continue to successfully connect to * the remote device just to immidiately close the physical connection again. * * WorkerThread and SocketConnectWorker are cleaned up via the threads * finished() signal. */ class SocketConnectWorker : public QObject { Q_OBJECT public: SocketConnectWorker(const QJniObject& socket, const QJniObject& targetUuid, const QBluetoothUuid& qtTargetUuid) : QObject(), mSocketObject(socket), mTargetUuid(targetUuid), mQtTargetUuid(qtTargetUuid) { static int t = qRegisterMetaType(); Q_UNUSED(t); } signals: void socketConnectDone(const QJniObject &socket); void socketConnectFailed(const QJniObject &socket, const QJniObject &targetUuid, const QBluetoothUuid &qtUuid); public slots: void connectSocket() { QJniEnvironment env; qCDebug(QT_BT_ANDROID) << "Connecting socket"; auto methodId = env.findMethod(mSocketObject.objectClass(), "connect", "()V"); if (methodId) env->CallVoidMethod(mSocketObject.object(), methodId); if (!methodId || env.checkAndClearExceptions()) { emit socketConnectFailed(mSocketObject, mTargetUuid, mQtTargetUuid); QThread::currentThread()->quit(); return; } qCDebug(QT_BT_ANDROID) << "Socket connection established"; emit socketConnectDone(mSocketObject); } void closeSocket() { qCDebug(QT_BT_ANDROID) << "Executing queued closeSocket()"; mSocketObject.callMethod("close"); QThread::currentThread()->quit(); } private: QJniObject mSocketObject; QJniObject mTargetUuid; // same as mTargetUuid above - just the Qt C++ version rather than jni uuid QBluetoothUuid mQtTargetUuid; }; class WorkerThread: public QThread { Q_OBJECT public: WorkerThread() : QThread(), workerPointer(0) { } // Runs in same thread as QBluetoothSocketPrivateAndroid void setupWorker(QBluetoothSocketPrivateAndroid* d_ptr, const QJniObject& socketObject, const QJniObject& uuidObject, bool useFallback, const QBluetoothUuid& qtUuid = QBluetoothUuid()) { SocketConnectWorker* worker = new SocketConnectWorker( socketObject, uuidObject, qtUuid); worker->moveToThread(this); connect(this, &QThread::finished, worker, &QObject::deleteLater); connect(this, &QThread::finished, this, &QObject::deleteLater); connect(d_ptr, &QBluetoothSocketPrivateAndroid::connectJavaSocket, worker, &SocketConnectWorker::connectSocket); connect(d_ptr, &QBluetoothSocketPrivateAndroid::closeJavaSocket, worker, &SocketConnectWorker::closeSocket); connect(worker, &SocketConnectWorker::socketConnectDone, d_ptr, &QBluetoothSocketPrivateAndroid::socketConnectSuccess); if (useFallback) { connect(worker, &SocketConnectWorker::socketConnectFailed, d_ptr, &QBluetoothSocketPrivateAndroid::fallbackSocketConnectFailed); } else { connect(worker, &SocketConnectWorker::socketConnectFailed, d_ptr, &QBluetoothSocketPrivateAndroid::defaultSocketConnectFailed); } workerPointer = worker; } private: QPointer workerPointer; }; QBluetoothSocketPrivateAndroid::QBluetoothSocketPrivateAndroid() : inputThread(0) { secFlags = QBluetooth::Security::Secure; adapter = getDefaultBluetoothAdapter(); qRegisterMetaType(); qRegisterMetaType(); } QBluetoothSocketPrivateAndroid::~QBluetoothSocketPrivateAndroid() { if (state != QBluetoothSocket::SocketState::UnconnectedState) emit closeJavaSocket(); } bool QBluetoothSocketPrivateAndroid::ensureNativeSocket(QBluetoothServiceInfo::Protocol type) { socketType = type; if (socketType == QBluetoothServiceInfo::RfcommProtocol) return true; return false; } /* * Workaround for QTBUG-61392. If the underlying Android bug gets fixed, * we need to consider restoring the non-reversed fallbackConnect from the repository. */ bool QBluetoothSocketPrivateAndroid::fallBackReversedConnect(const QBluetoothUuid &uuid) { Q_Q(QBluetoothSocket); qCWarning(QT_BT_ANDROID) << "Falling back to reverse uuid workaround."; const QBluetoothUuid reverse = reverseUuid(uuid); if (reverse.isNull()) return false; QString tempUuid = reverse.toString(QUuid::WithoutBraces); QJniEnvironment env; const QJniObject inputString = QJniObject::fromString(tempUuid); const QJniObject uuidObject = QJniObject::callStaticMethod( QtJniTypes::Traits::className(), "fromString", inputString.object()); if (secFlags == QBluetooth::SecurityFlags(QBluetooth::Security::NoSecurity)) { qCDebug(QT_BT_ANDROID) << "Connecting via insecure rfcomm"; socketObject = remoteDevice.callMethod( "createInsecureRfcommSocketToServiceRecord", uuidObject.object()); } else { qCDebug(QT_BT_ANDROID) << "Connecting via secure rfcomm"; socketObject = remoteDevice.callMethod( "createRfcommSocketToServiceRecord", uuidObject.object()); } if (!socketObject.isValid()) { remoteDevice = QJniObject(); errorString = QBluetoothSocket::tr("Cannot connect to %1", "%1 = uuid").arg(reverse.toString()); q->setSocketError(QBluetoothSocket::SocketError::ServiceNotFoundError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); return false; } WorkerThread *workerThread = new WorkerThread(); workerThread->setupWorker(this, socketObject, uuidObject, USE_FALLBACK); workerThread->start(); emit connectJavaSocket(); return true; } /* * The call order during a connectToServiceHelper() is as follows: * * 1. call connectToServiceHelper() * 2. wait for execution of SocketConnectThread::run() * 3. if threaded connect succeeds call socketConnectSuccess() via signals * -> done * 4. if threaded connect fails call defaultSocketConnectFailed() via signals * 5. call fallBackReversedConnect() * -> if failure entire connectToServiceHelper() fails * Note: This fallback can be disabled with private API boolean * 6. if threaded connect on one of above fallbacks succeeds call socketConnectSuccess() * via signals * -> done * 7. if threaded connect on fallback channel fails call fallbackSocketConnectFailed() * -> complete failure of entire connectToServiceHelper() * */ void QBluetoothSocketPrivateAndroid::connectToServiceHelper(const QBluetoothAddress &address, const QBluetoothUuid &uuid, QIODevice::OpenMode openMode) { Q_Q(QBluetoothSocket); Q_UNUSED(openMode); qCDebug(QT_BT_ANDROID) << "connectToServiceHelper()" << address.toString() << uuid.toString(); if (!ensureAndroidPermission(QBluetoothPermission::Access)) { qCWarning(QT_BT_ANDROID) << "Bluetooth socket connect failed due to missing permissions"; errorString = QBluetoothSocket::tr( "Bluetooth socket connect failed due to missing permissions."); q->setSocketError(QBluetoothSocket::SocketError::MissingPermissionsError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); return; } q->setSocketState(QBluetoothSocket::SocketState::ConnectingState); if (!adapter.isValid()) { qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth"; errorString = QBluetoothSocket::tr("Device does not support Bluetooth"); q->setSocketError(QBluetoothSocket::SocketError::NetworkError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); return; } const int state = adapter.callMethod("getState"); if (state != 12 ) { //BluetoothAdapter.STATE_ON qCWarning(QT_BT_ANDROID) << "Bluetooth device offline"; errorString = QBluetoothSocket::tr("Device is powered off"); q->setSocketError(QBluetoothSocket::SocketError::NetworkError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); return; } QJniEnvironment env; QJniObject inputString = QJniObject::fromString(address.toString()); remoteDevice = adapter.callMethod("getRemoteDevice", inputString.object()); if (!remoteDevice.isValid()) { errorString = QBluetoothSocket::tr("Cannot access address %1", "%1 = Bt address e.g. 11:22:33:44:55:66").arg(address.toString()); q->setSocketError(QBluetoothSocket::SocketError::HostNotFoundError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); return; } //cut leading { and trailing } {xxx-xxx} const QString tempUuid = uuid.toString(QUuid::WithoutBraces); inputString = QJniObject::fromString(tempUuid); const QJniObject uuidObject = QJniObject::callStaticMethod( QtJniTypes::Traits::className(), "fromString", inputString.object()); if (secFlags == QBluetooth::SecurityFlags(QBluetooth::Security::NoSecurity)) { qCDebug(QT_BT_ANDROID) << "Connecting via insecure rfcomm"; socketObject = remoteDevice.callMethod( "createInsecureRfcommSocketToServiceRecord", uuidObject.object()); } else { qCDebug(QT_BT_ANDROID) << "Connecting via secure rfcomm"; socketObject = remoteDevice.callMethod( "createRfcommSocketToServiceRecord", uuidObject.object()); } if (!socketObject.isValid()) { remoteDevice = QJniObject(); errorString = QBluetoothSocket::tr("Cannot connect to %1 on %2", "%1 = uuid, %2 = Bt address").arg(uuid.toString()).arg(address.toString()); q->setSocketError(QBluetoothSocket::SocketError::ServiceNotFoundError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); return; } WorkerThread *workerThread = new WorkerThread(); workerThread->setupWorker(this, socketObject, uuidObject, !USE_FALLBACK, uuid); workerThread->start(); emit connectJavaSocket(); } void QBluetoothSocketPrivateAndroid::connectToService( const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode) { Q_Q(QBluetoothSocket); if (q->state() != QBluetoothSocket::SocketState::UnconnectedState && q->state() != QBluetoothSocket::SocketState::ServiceLookupState) { qCWarning(QT_BT_ANDROID) << "QBluetoothSocketPrivateAndroid::connectToService called on busy socket"; errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); q->setSocketError(QBluetoothSocket::SocketError::OperationError); return; } // Workaround for QTBUG-75035 /* Not all Android devices publish or discover the SPP uuid for serial services. * Also, Android does not permit the detection of the protocol used by a serial * Bluetooth connection. * * Therefore, QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices() * may have to guess what protocol a potential custom uuid uses. The guessing works * reasonably well as long as the SDP discovery finds the SPP uuid. Otherwise * the SPP and rfcomm protocol info is missing in \a service. * * Android only supports RFCOMM (no L2CP). We assume (in favor of user experience) * that a non-RFCOMM protocol implies a missing SPP uuid during discovery but the user * still wanting to connect with the given \a service instance. */ auto protocol = service.socketProtocol(); switch (protocol) { case QBluetoothServiceInfo::L2capProtocol: case QBluetoothServiceInfo::UnknownProtocol: qCWarning(QT_BT_ANDROID) << "Changing socket protocol to RFCOMM"; protocol = QBluetoothServiceInfo::RfcommProtocol; break; case QBluetoothServiceInfo::RfcommProtocol: break; } if (!ensureNativeSocket(protocol)) { errorString = QBluetoothSocket::tr("Socket type not supported"); q->setSocketError(QBluetoothSocket::SocketError::UnsupportedProtocolError); return; } connectToServiceHelper(service.device().address(), service.serviceUuid(), openMode); } void QBluetoothSocketPrivateAndroid::connectToService( const QBluetoothAddress &address, const QBluetoothUuid &uuid, QIODevice::OpenMode openMode) { Q_Q(QBluetoothSocket); if (q->state() != QBluetoothSocket::SocketState::UnconnectedState) { qCWarning(QT_BT_ANDROID) << "QBluetoothSocketPrivateAndroid::connectToService called on busy socket"; errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); q->setSocketError(QBluetoothSocket::SocketError::OperationError); return; } if (q->socketType() == QBluetoothServiceInfo::UnknownProtocol) { qCWarning(QT_BT_ANDROID) << "QBluetoothSocketPrivateAndroid::connectToService cannot " "connect with 'UnknownProtocol' (type provided by given service)"; errorString = QBluetoothSocket::tr("Socket type not supported"); q->setSocketError(QBluetoothSocket::SocketError::UnsupportedProtocolError); return; } if (!ensureNativeSocket(q->socketType())) { errorString = QBluetoothSocket::tr("Socket type not supported"); q->setSocketError(QBluetoothSocket::SocketError::UnsupportedProtocolError); return; } connectToServiceHelper(address, uuid, openMode); } void QBluetoothSocketPrivateAndroid::connectToService( const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) { Q_UNUSED(port); Q_UNUSED(openMode); Q_UNUSED(address); Q_Q(QBluetoothSocket); errorString = tr("Connecting to port is not supported"); q->setSocketError(QBluetoothSocket::SocketError::ServiceNotFoundError); qCWarning(QT_BT_ANDROID) << "Connecting to port is not supported"; } void QBluetoothSocketPrivateAndroid::socketConnectSuccess(const QJniObject &socket) { Q_Q(QBluetoothSocket); QJniEnvironment env; // test we didn't get a success from a previous connect // which was cleaned up late if (socket != socketObject) return; if (inputThread) { inputThread->deleteLater(); inputThread = 0; } inputStream = socketObject.callMethod("getInputStream"); outputStream = socketObject.callMethod("getOutputStream"); if (!inputStream.isValid() || !outputStream.isValid()) { emit closeJavaSocket(); socketObject = inputStream = outputStream = remoteDevice = QJniObject(); errorString = QBluetoothSocket::tr("Obtaining streams for service failed"); q->setSocketError(QBluetoothSocket::SocketError::NetworkError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); return; } inputThread = new InputStreamThread(this); QObject::connect(inputThread, SIGNAL(dataAvailable()), q, SIGNAL(readyRead()), Qt::QueuedConnection); QObject::connect(inputThread, SIGNAL(errorOccurred(int)), this, SLOT(inputThreadError(int)), Qt::QueuedConnection); if (!inputThread->run()) { //close socket again emit closeJavaSocket(); socketObject = inputStream = outputStream = remoteDevice = QJniObject(); delete inputThread; inputThread = 0; errorString = QBluetoothSocket::tr("Input stream thread cannot be started"); q->setSocketError(QBluetoothSocket::SocketError::NetworkError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); return; } // only unbuffered behavior supported at this stage q->setOpenMode(QIODevice::ReadWrite|QIODevice::Unbuffered); q->setSocketState(QBluetoothSocket::SocketState::ConnectedState); } void QBluetoothSocketPrivateAndroid::defaultSocketConnectFailed( const QJniObject &socket, const QJniObject &targetUuid, const QBluetoothUuid &qtTargetUuid) { Q_UNUSED(targetUuid); Q_Q(QBluetoothSocket); // test we didn't get a fail from a previous connect // which was cleaned up late - should be same socket if (socket != socketObject) return; if (!useReverseUuidWorkAroundConnect || !fallBackReversedConnect(qtTargetUuid)) { errorString = QBluetoothSocket::tr("Connection to service failed"); socketObject = remoteDevice = QJniObject(); q->setSocketError(QBluetoothSocket::SocketError::ServiceNotFoundError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); qCWarning(QT_BT_ANDROID) << "Socket connect workaround failed"; } } void QBluetoothSocketPrivateAndroid::fallbackSocketConnectFailed( const QJniObject &socket, const QJniObject &targetUuid) { Q_UNUSED(targetUuid); Q_Q(QBluetoothSocket); // test we didn't get a fail from a previous connect // which was cleaned up late - should be same socket if (socket != socketObject) return; qCWarning(QT_BT_ANDROID) << "Socket connect via workaround failed."; errorString = QBluetoothSocket::tr("Connection to service failed"); socketObject = remoteDevice = QJniObject(); q->setSocketError(QBluetoothSocket::SocketError::ServiceNotFoundError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); } void QBluetoothSocketPrivateAndroid::abort() { if (state == QBluetoothSocket::SocketState::UnconnectedState) return; if (socketObject.isValid()) { QJniEnvironment env; /* * BluetoothSocket.close() triggers an abort of the input stream * thread because inputStream.read() throws IOException * In turn the thread stops and throws an error which sets * new state, error and emits relevant signals. * See QBluetoothSocketPrivateAndroid::inputThreadError() for details */ if (inputThread) inputThread->prepareForClosure(); emit closeJavaSocket(); inputStream = outputStream = socketObject = remoteDevice = QJniObject(); if (inputThread) { // inputThread exists hence we had a successful connect // which means inputThread is responsible for setting Unconnected //don't delete here as signals caused by Java Thread are still //going to be emitted //delete occurs in inputThreadError() inputThread = 0; } else { // inputThread doesn't exist hence // we abort in the middle of connect(). WorkerThread will do // close() without further feedback. Therefore we have to set // Unconnected (now) in advance Q_Q(QBluetoothSocket); q->setOpenMode(QIODevice::NotOpen); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); emit q->readChannelFinished(); } } } QString QBluetoothSocketPrivateAndroid::localName() const { if (!ensureAndroidPermission(QBluetoothPermission::Access)) { qCWarning(QT_BT_ANDROID) << "Bluetooth socket localName() failed due to" "missing permissions"; } else if (adapter.isValid()) { return adapter.callMethod("getName").toString(); } return QString(); } QBluetoothAddress QBluetoothSocketPrivateAndroid::localAddress() const { QString result; if (!ensureAndroidPermission(QBluetoothPermission::Access)) { qCWarning(QT_BT_ANDROID) << "Bluetooth socket localAddress() failed due to" "missing permissions"; } else if (adapter.isValid()) { result = adapter.callMethod("getAddress").toString(); } return QBluetoothAddress(result); } quint16 QBluetoothSocketPrivateAndroid::localPort() const { // Impossible to get channel number with current Android API (Levels 5 to 19) return 0; } QString QBluetoothSocketPrivateAndroid::peerName() const { if (!remoteDevice.isValid()) return QString(); return remoteDevice.callMethod("getName").toString(); } QBluetoothAddress QBluetoothSocketPrivateAndroid::peerAddress() const { if (!remoteDevice.isValid()) return QBluetoothAddress(); const QString address = remoteDevice.callMethod("getAddress").toString(); return QBluetoothAddress(address); } quint16 QBluetoothSocketPrivateAndroid::peerPort() const { // Impossible to get channel number with current Android API (Levels 5 to 13) return 0; } qint64 QBluetoothSocketPrivateAndroid::writeData(const char *data, qint64 maxSize) { //TODO implement buffered behavior (so far only unbuffered) Q_Q(QBluetoothSocket); if (state != QBluetoothSocket::SocketState::ConnectedState || !outputStream.isValid()) { qCWarning(QT_BT_ANDROID) << "Socket::writeData: " << state << outputStream.isValid(); errorString = QBluetoothSocket::tr("Cannot write while not connected"); q->setSocketError(QBluetoothSocket::SocketError::OperationError); return -1; } QJniEnvironment env; jbyteArray nativeData = env->NewByteArray((qint32)maxSize); env->SetByteArrayRegion(nativeData, 0, (qint32)maxSize, reinterpret_cast(data)); auto methodId = env.findMethod(outputStream.objectClass(), "write", "([BII)V"); if (methodId) env->CallVoidMethod(outputStream.object(), methodId, nativeData, 0, (qint32)maxSize); env->DeleteLocalRef(nativeData); if (!methodId || env.checkAndClearExceptions()) { qCWarning(QT_BT_ANDROID) << "Error while writing"; errorString = QBluetoothSocket::tr("Error during write on socket."); q->setSocketError(QBluetoothSocket::SocketError::NetworkError); return -1; } emit q->bytesWritten(maxSize); return maxSize; } qint64 QBluetoothSocketPrivateAndroid::readData(char *data, qint64 maxSize) { Q_Q(QBluetoothSocket); if (state != QBluetoothSocket::SocketState::ConnectedState || !inputThread) { qCWarning(QT_BT_ANDROID) << "Socket::readData: " << state << inputThread ; errorString = QBluetoothSocket::tr("Cannot read while not connected"); q->setSocketError(QBluetoothSocket::SocketError::OperationError); return -1; } return inputThread->readData(data, maxSize); } void QBluetoothSocketPrivateAndroid::inputThreadError(int errorCode) { Q_Q(QBluetoothSocket); if (errorCode != -1) { //magic error which is expected and can be ignored errorString = QBluetoothSocket::tr("Network error during read"); q->setSocketError(QBluetoothSocket::SocketError::NetworkError); } //finally we can delete the InputStreamThread InputStreamThread *client = qobject_cast(sender()); if (client) client->deleteLater(); if (socketObject.isValid()) { //triggered when remote side closed the socket //cleanup internal objects //if it was call to local close()/abort() the objects are cleaned up already emit closeJavaSocket(); inputStream = outputStream = remoteDevice = socketObject = QJniObject(); if (inputThread) { // deleted already above (client->deleteLater()) inputThread = 0; } } q->setOpenMode(QIODevice::NotOpen); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); emit q->readChannelFinished(); } void QBluetoothSocketPrivateAndroid::close() { /* This function is called by QBluetoothSocket::close and softer version QBluetoothSocket::disconnectFromService() which difference I do not quite fully understand. Anyways we end up in Android "close" function call. */ abort(); } bool QBluetoothSocketPrivateAndroid::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode) { Q_UNUSED(socketDescriptor); Q_UNUSED(socketType); Q_UNUSED(socketState); Q_UNUSED(openMode); qCWarning(QT_BT_ANDROID) << "No socket descriptor support on Android."; return false; } bool QBluetoothSocketPrivateAndroid::setSocketDescriptor(const QJniObject &socket, QBluetoothServiceInfo::Protocol socketType_, QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode) { Q_Q(QBluetoothSocket); if (q->state() != QBluetoothSocket::SocketState::UnconnectedState || !socket.isValid()) return false; if (!ensureNativeSocket(socketType_)) return false; socketObject = socket; inputStream = socketObject.callMethod("getInputStream"); outputStream = socketObject.callMethod("getOutputStream"); if (!inputStream.isValid() || !outputStream.isValid()) { //close socket again socketObject.callMethod("close"); socketObject = inputStream = outputStream = remoteDevice = QJniObject(); errorString = QBluetoothSocket::tr("Obtaining streams for service failed"); q->setSocketError(QBluetoothSocket::SocketError::NetworkError); q->setSocketState(QBluetoothSocket::SocketState::UnconnectedState); return false; } remoteDevice = socketObject.callMethod("getRemoteDevice"); if (inputThread) { inputThread->deleteLater(); inputThread = 0; } inputThread = new InputStreamThread(this); QObject::connect(inputThread, SIGNAL(dataAvailable()), q, SIGNAL(readyRead()), Qt::QueuedConnection); QObject::connect(inputThread, SIGNAL(errorOccurred(int)), this, SLOT(inputThreadError(int)), Qt::QueuedConnection); inputThread->run(); // WorkerThread manages all sockets for us // When we come through here the socket was already connected by // server socket listener (see QBluetoothServer) // Therefore we only use WorkerThread to potentially close it later on WorkerThread *workerThread = new WorkerThread(); workerThread->setupWorker(this, socketObject, QJniObject(), !USE_FALLBACK); workerThread->start(); q->setOpenMode(openMode | QIODevice::Unbuffered); q->setSocketState(socketState); return true; } qint64 QBluetoothSocketPrivateAndroid::bytesAvailable() const { //We cannot access buffer directly as it is part of different thread if (inputThread) return inputThread->bytesAvailable(); return 0; } qint64 QBluetoothSocketPrivateAndroid::bytesToWrite() const { return 0; // nothing because always unbuffered } /* * This function is part of a workaround for QTBUG-61392 * * Returns null uuid if the given \a serviceUuid is not a uuid * derived from the Bluetooth base uuid. */ QBluetoothUuid QBluetoothSocketPrivateAndroid::reverseUuid(const QBluetoothUuid &serviceUuid) { if (serviceUuid.isNull()) return QBluetoothUuid(); bool isBaseUuid = false; serviceUuid.toUInt32(&isBaseUuid); if (isBaseUuid) return serviceUuid; const QUuid::Id128Bytes original = serviceUuid.toBytes(); QUuid::Id128Bytes reversed; for (int i = 0; i < 16; i++) reversed.data[15-i] = original.data[i]; return QBluetoothUuid{reversed}; } bool QBluetoothSocketPrivateAndroid::canReadLine() const { // We cannot access buffer directly as it is part of different thread if (inputThread) return inputThread->canReadLine(); return false; } QT_END_NAMESPACE #include