Revision control

Copy as Markdown

// Copyright (C) 2016 Lauri Laanmets (Proekspert AS) <lauri.laanmets@eesti.ee>
// 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 <QCoreApplication>
#include <QtCore/QLoggingCategory>
#include <QtCore/QThread>
#include <QtCore/QTime>
#include <QtCore/QJniEnvironment>
#include <QtCore/q26numeric.h>
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<QJniObject>();
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<void>("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<SocketConnectWorker> workerPointer;
};
QBluetoothSocketPrivateAndroid::QBluetoothSocketPrivateAndroid(QBluetoothSocket *qq)
: QBluetoothSocketBasePrivate{qq},
inputThread(0)
{
secFlags = QBluetooth::Security::Secure;
adapter = getDefaultBluetoothAdapter();
qRegisterMetaType<QBluetoothSocket::SocketError>();
qRegisterMetaType<QBluetoothSocket::SocketState>();
}
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::UUID>(
QtJniTypes::Traits<QtJniTypes::UUID>::className(), "fromString",
inputString.object<jstring>());
if (secFlags == QBluetooth::SecurityFlags(QBluetooth::Security::NoSecurity)) {
qCDebug(QT_BT_ANDROID) << "Connecting via insecure rfcomm";
socketObject = remoteDevice.callMethod<QtJniTypes::BluetoothSocket>(
"createInsecureRfcommSocketToServiceRecord",
uuidObject.object<QtJniTypes::UUID>());
} else {
qCDebug(QT_BT_ANDROID) << "Connecting via secure rfcomm";
socketObject = remoteDevice.callMethod<QtJniTypes::BluetoothSocket>(
"createRfcommSocketToServiceRecord",
uuidObject.object<QtJniTypes::UUID>());
}
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<jint>("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<QtJniTypes::BluetoothDevice>("getRemoteDevice",
inputString.object<jstring>());
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::UUID>(
QtJniTypes::Traits<QtJniTypes::UUID>::className(), "fromString",
inputString.object<jstring>());
if (secFlags == QBluetooth::SecurityFlags(QBluetooth::Security::NoSecurity)) {
qCDebug(QT_BT_ANDROID) << "Connecting via insecure rfcomm";
socketObject = remoteDevice.callMethod<QtJniTypes::BluetoothSocket>(
"createInsecureRfcommSocketToServiceRecord",
uuidObject.object<QtJniTypes::UUID>());
} else {
qCDebug(QT_BT_ANDROID) << "Connecting via secure rfcomm";
socketObject = remoteDevice.callMethod<QtJniTypes::BluetoothSocket>(
"createRfcommSocketToServiceRecord",
uuidObject.object<QtJniTypes::UUID>());
}
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<QtJniTypes::InputStream>("getInputStream");
outputStream = socketObject.callMethod<QtJniTypes::OutputStream>("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<jstring>("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<jstring>("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<jstring>("getName").toString();
}
QBluetoothAddress QBluetoothSocketPrivateAndroid::peerAddress() const
{
if (!remoteDevice.isValid())
return QBluetoothAddress();
const QString address = remoteDevice.callMethod<jstring>("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;
}
const qint32 javaSize = q26::saturate_cast<qint32>(maxSize);
QJniEnvironment env;
jbyteArray nativeData = env->NewByteArray(javaSize);
env->SetByteArrayRegion(nativeData, 0, javaSize, reinterpret_cast<const jbyte*>(data));
auto methodId = env.findMethod(outputStream.objectClass(),
"write",
"([BII)V");
if (methodId)
env->CallVoidMethod(outputStream.object(), methodId, nativeData, 0, javaSize);
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(javaSize);
return javaSize;
}
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<InputStreamThread *>(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<QtJniTypes::InputStream>("getInputStream");
outputStream = socketObject.callMethod<QtJniTypes::OutputStream>("getOutputStream");
if (!inputStream.isValid() || !outputStream.isValid()) {
//close socket again
socketObject.callMethod<void>("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<QtJniTypes::BluetoothDevice>("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 <qbluetoothsocket_android.moc>