/**************************************************************************** ** ** Copyright (C) 2016 Lauri Laanmets (Proekspert AS) ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qbluetoothsocket.h" #include "qbluetoothsocket_android_p.h" #include "qbluetoothaddress.h" #include "qbluetoothdeviceinfo.h" #include "qbluetoothserviceinfo.h" #include #include #include #include #include #include QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID) #define FALLBACK_CHANNEL 1 #define USE_FALLBACK true Q_DECLARE_METATYPE(QAndroidJniObject) 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 QAndroidJniObject& socket, const QAndroidJniObject& targetUuid, const QBluetoothUuid& qtTargetUuid) : QObject(), mSocketObject(socket), mTargetUuid(targetUuid), mQtTargetUuid(qtTargetUuid) { static int t = qRegisterMetaType(); Q_UNUSED(t); } signals: void socketConnectDone(const QAndroidJniObject &socket); void socketConnectFailed(const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid, const QBluetoothUuid &qtUuid); public slots: void connectSocket() { QAndroidJniEnvironment env; qCDebug(QT_BT_ANDROID) << "Connecting socket"; mSocketObject.callMethod("connect"); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); 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()"; QAndroidJniEnvironment env; mSocketObject.callMethod("close"); if (env->ExceptionCheck()) { qCWarning(QT_BT_ANDROID) << "Error during closure of abandoned socket"; env->ExceptionDescribe(); env->ExceptionClear(); } QThread::currentThread()->quit(); } private: QAndroidJniObject mSocketObject; QAndroidJniObject 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 QAndroidJniObject& socketObject, const QAndroidJniObject& 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::Secure; adapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter", "getDefaultAdapter", "()Landroid/bluetooth/BluetoothAdapter;"); qRegisterMetaType(); qRegisterMetaType(); } QBluetoothSocketPrivateAndroid::~QBluetoothSocketPrivateAndroid() { if (state != QBluetoothSocket::UnconnectedState) emit closeJavaSocket(); } bool QBluetoothSocketPrivateAndroid::ensureNativeSocket(QBluetoothServiceInfo::Protocol type) { socketType = type; if (socketType == QBluetoothServiceInfo::RfcommProtocol) return true; return false; } bool QBluetoothSocketPrivateAndroid::fallBackConnect(QAndroidJniObject uuid, int channel) { qCWarning(QT_BT_ANDROID) << "Falling back to getServiceChannel() workaround."; QAndroidJniEnvironment env; QAndroidJniObject remoteDeviceClass = remoteDevice.callObjectMethod("getClass", "()Ljava/lang/Class;"); if (!remoteDeviceClass.isValid()) { qCWarning(QT_BT_ANDROID) << "Could not invoke BluetoothDevice.getClass."; return false; } QAndroidJniObject integerObject = QAndroidJniObject::getStaticObjectField( "java/lang/Integer", "TYPE", "Ljava/lang/Class;"); if (!integerObject.isValid()) { qCWarning(QT_BT_ANDROID) << "Could not get Integer.TYPE"; if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } return false; } jclass classClass = QJNIEnvironmentPrivate::findClass("java/lang/Class"); jobjectArray rawArray = env->NewObjectArray(1, classClass, integerObject.object()); QAndroidJniObject paramTypes(rawArray); env->DeleteLocalRef(rawArray); if (!paramTypes.isValid()) { qCWarning(QT_BT_ANDROID) << "Could not create new Class[]{Integer.TYPE}"; if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } return false; } QAndroidJniObject parcelUuid("android/os/ParcelUuid", "(Ljava/util/UUID;)V", uuid.object()); if (parcelUuid.isValid()) { jint socketChannel = remoteDevice.callMethod("getServiceChannel", "(Landroid/os/ParcelUuid;)I", parcelUuid.object()); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } else { if (socketChannel == remoteDevice.getStaticField("android/bluetooth/BluetoothDevice", "ERROR") || socketChannel == -1) { qCWarning(QT_BT_ANDROID) << "Cannot determine RFCOMM service channel."; } else { qCWarning(QT_BT_ANDROID) << "Using found rfcomm channel" << socketChannel; channel = socketChannel; } } } QAndroidJniObject method; if (secFlags == QBluetooth::NoSecurity) { qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm"; method = remoteDeviceClass.callObjectMethod( "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", QAndroidJniObject::fromString(QLatin1String("createInsecureRfcommSocket")).object(), paramTypes.object()); } else { qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm"; method = remoteDeviceClass.callObjectMethod( "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", QAndroidJniObject::fromString(QLatin1String("createRfcommSocket")).object(), paramTypes.object()); } if (!method.isValid() || env->ExceptionCheck()) { qCWarning(QT_BT_ANDROID) << "Could not invoke getMethod"; if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } return false; } jclass objectClass = QJNIEnvironmentPrivate::findClass("java/lang/Object"); QAndroidJniObject channelObject = QAndroidJniObject::callStaticObjectMethod( "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", channel); rawArray = env->NewObjectArray(1, objectClass, channelObject.object()); QAndroidJniObject invokeResult = method.callObjectMethod("invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", remoteDevice.object(), rawArray); env->DeleteLocalRef(rawArray); if (!invokeResult.isValid()) { qCWarning(QT_BT_ANDROID) << "Invoke Resulted with error."; if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } return false; } socketObject = QAndroidJniObject(invokeResult); WorkerThread *workerThread = new WorkerThread(); workerThread->setupWorker(this, socketObject, uuid, USE_FALLBACK); workerThread->start(); emit connectJavaSocket(); qCWarning(QT_BT_ANDROID) << "Workaround thread invoked."; return true; } /* * Workaround for QTBUG-61392 */ 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; //cut leading { and trailing } {xxx-xxx} QString tempUuid = reverse.toString(); tempUuid.chop(1); //remove trailing '}' tempUuid.remove(0, 1); //remove first '{' QAndroidJniEnvironment env; const QAndroidJniObject inputString = QAndroidJniObject::fromString(tempUuid); const QAndroidJniObject uuidObject = QAndroidJniObject::callStaticObjectMethod("java/util/UUID", "fromString", "(Ljava/lang/String;)Ljava/util/UUID;", inputString.object()); if (secFlags == QBluetooth::NoSecurity) { qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm"; socketObject = remoteDevice.callObjectMethod("createInsecureRfcommSocketToServiceRecord", "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", uuidObject.object()); } else { qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm"; socketObject = remoteDevice.callObjectMethod("createRfcommSocketToServiceRecord", "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", uuidObject.object()); } if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); socketObject = remoteDevice = QAndroidJniObject(); errorString = QBluetoothSocket::tr("Cannot connect to %1", "%1 = uuid").arg(reverse.toString()); q->setSocketError(QBluetoothSocket::ServiceNotFoundError); q->setSocketState(QBluetoothSocket::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 fallBackConnect() if Android version 22 or below * -> Android 23+ complete failure of entire connectToServiceHelper() * 6. call fallBackReversedConnect() if Android version 23 or above * -> if failure entire connectToServiceHelper() fails * 7. if threaded connect on one of above fallbacks succeeds call socketConnectSuccess() * via signals * -> done * 8. 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(); q->setSocketState(QBluetoothSocket::ConnectingState); if (!adapter.isValid()) { qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth"; errorString = QBluetoothSocket::tr("Device does not support Bluetooth"); q->setSocketError(QBluetoothSocket::NetworkError); q->setSocketState(QBluetoothSocket::UnconnectedState); return; } const int state = adapter.callMethod("getState"); if (state != 12 ) { //BluetoothAdapter.STATE_ON qCWarning(QT_BT_ANDROID) << "Bt device offline"; errorString = QBluetoothSocket::tr("Device is powered off"); q->setSocketError(QBluetoothSocket::NetworkError); q->setSocketState(QBluetoothSocket::UnconnectedState); return; } QAndroidJniEnvironment env; QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString()); remoteDevice = adapter.callObjectMethod("getRemoteDevice", "(Ljava/lang/String;)Landroid/bluetooth/BluetoothDevice;", inputString.object()); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); errorString = QBluetoothSocket::tr("Cannot access address %1", "%1 = Bt address e.g. 11:22:33:44:55:66").arg(address.toString()); q->setSocketError(QBluetoothSocket::HostNotFoundError); q->setSocketState(QBluetoothSocket::UnconnectedState); return; } //cut leading { and trailing } {xxx-xxx} QString tempUuid = uuid.toString(); tempUuid.chop(1); //remove trailing '}' tempUuid.remove(0, 1); //remove first '{' inputString = QAndroidJniObject::fromString(tempUuid); QAndroidJniObject uuidObject = QAndroidJniObject::callStaticObjectMethod("java/util/UUID", "fromString", "(Ljava/lang/String;)Ljava/util/UUID;", inputString.object()); if (secFlags == QBluetooth::NoSecurity) { qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm"; socketObject = remoteDevice.callObjectMethod("createInsecureRfcommSocketToServiceRecord", "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", uuidObject.object()); } else { qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm"; socketObject = remoteDevice.callObjectMethod("createRfcommSocketToServiceRecord", "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", uuidObject.object()); } if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); socketObject = remoteDevice = QAndroidJniObject(); errorString = QBluetoothSocket::tr("Cannot connect to %1 on %2", "%1 = uuid, %2 = Bt address").arg(uuid.toString()).arg(address.toString()); q->setSocketError(QBluetoothSocket::ServiceNotFoundError); q->setSocketState(QBluetoothSocket::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::UnconnectedState && q->state() != QBluetoothSocket::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::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::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::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::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::UnsupportedProtocolError); return; } if (!ensureNativeSocket(q->socketType())) { errorString = QBluetoothSocket::tr("Socket type not supported"); q->setSocketError(QBluetoothSocket::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::ServiceNotFoundError); qCWarning(QT_BT_ANDROID) << "Connecting to port is not supported"; } void QBluetoothSocketPrivateAndroid::socketConnectSuccess(const QAndroidJniObject &socket) { Q_Q(QBluetoothSocket); QAndroidJniEnvironment 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.callObjectMethod("getInputStream", "()Ljava/io/InputStream;"); outputStream = socketObject.callObjectMethod("getOutputStream", "()Ljava/io/OutputStream;"); if (env->ExceptionCheck() || !inputStream.isValid() || !outputStream.isValid()) { env->ExceptionDescribe(); env->ExceptionClear(); emit closeJavaSocket(); socketObject = inputStream = outputStream = remoteDevice = QAndroidJniObject(); errorString = QBluetoothSocket::tr("Obtaining streams for service failed"); q->setSocketError(QBluetoothSocket::NetworkError); q->setSocketState(QBluetoothSocket::UnconnectedState); return; } inputThread = new InputStreamThread(this); QObject::connect(inputThread, SIGNAL(dataAvailable()), q, SIGNAL(readyRead()), Qt::QueuedConnection); QObject::connect(inputThread, SIGNAL(error(int)), this, SLOT(inputThreadError(int)), Qt::QueuedConnection); if (!inputThread->run()) { //close socket again emit closeJavaSocket(); socketObject = inputStream = outputStream = remoteDevice = QAndroidJniObject(); delete inputThread; inputThread = 0; errorString = QBluetoothSocket::tr("Input stream thread cannot be started"); q->setSocketError(QBluetoothSocket::NetworkError); q->setSocketState(QBluetoothSocket::UnconnectedState); return; } // only unbuffered behavior supported at this stage q->setOpenMode(QIODevice::ReadWrite|QIODevice::Unbuffered); q->setSocketState(QBluetoothSocket::ConnectedState); } void QBluetoothSocketPrivateAndroid::defaultSocketConnectFailed( const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid, const QBluetoothUuid &qtTargetUuid) { 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; bool success = false; if (QtAndroid::androidSdkVersion() <= 22) success = fallBackConnect(targetUuid, FALLBACK_CHANNEL); else if (useReverseUuidWorkAroundConnect) // version 23+ has Android bug (see QTBUG-61392) success = fallBackReversedConnect(qtTargetUuid); if (!success) { errorString = QBluetoothSocket::tr("Connection to service failed"); socketObject = remoteDevice = QAndroidJniObject(); q->setSocketError(QBluetoothSocket::ServiceNotFoundError); q->setSocketState(QBluetoothSocket::UnconnectedState); QAndroidJniEnvironment env; env->ExceptionClear(); // just in case qCWarning(QT_BT_ANDROID) << "Workaround failed"; } } void QBluetoothSocketPrivateAndroid::fallbackSocketConnectFailed( const QAndroidJniObject &socket, const QAndroidJniObject &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 = QAndroidJniObject(); q->setSocketError(QBluetoothSocket::ServiceNotFoundError); q->setSocketState(QBluetoothSocket::UnconnectedState); } void QBluetoothSocketPrivateAndroid::abort() { if (state == QBluetoothSocket::UnconnectedState) return; if (socketObject.isValid()) { QAndroidJniEnvironment 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 = QAndroidJniObject(); 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::UnconnectedState); emit q->readChannelFinished(); } } } QString QBluetoothSocketPrivateAndroid::localName() const { if (adapter.isValid()) return adapter.callObjectMethod("getName").toString(); return QString(); } QBluetoothAddress QBluetoothSocketPrivateAndroid::localAddress() const { QString result; if (adapter.isValid()) result = adapter.callObjectMethod("getAddress", "()Ljava/lang/String;").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.callObjectMethod("getName", "()Ljava/lang/String;").toString(); } QBluetoothAddress QBluetoothSocketPrivateAndroid::peerAddress() const { if (!remoteDevice.isValid()) return QBluetoothAddress(); const QString address = remoteDevice.callObjectMethod("getAddress", "()Ljava/lang/String;").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::ConnectedState || !outputStream.isValid()) { qCWarning(QT_BT_ANDROID) << "Socket::writeData: " << state << outputStream.isValid(); errorString = QBluetoothSocket::tr("Cannot write while not connected"); q->setSocketError(QBluetoothSocket::OperationError); return -1; } QAndroidJniEnvironment env; jbyteArray nativeData = env->NewByteArray((qint32)maxSize); env->SetByteArrayRegion(nativeData, 0, (qint32)maxSize, reinterpret_cast(data)); outputStream.callMethod("write", "([BII)V", nativeData, 0, (qint32)maxSize); env->DeleteLocalRef(nativeData); if (env->ExceptionCheck()) { qCWarning(QT_BT_ANDROID) << "Error while writing"; env->ExceptionDescribe(); env->ExceptionClear(); errorString = QBluetoothSocket::tr("Error during write on socket."); q->setSocketError(QBluetoothSocket::NetworkError); return -1; } emit q->bytesWritten(maxSize); return maxSize; } qint64 QBluetoothSocketPrivateAndroid::readData(char *data, qint64 maxSize) { Q_Q(QBluetoothSocket); if (state != QBluetoothSocket::ConnectedState || !inputThread) { qCWarning(QT_BT_ANDROID) << "Socket::readData: " << state << inputThread ; errorString = QBluetoothSocket::tr("Cannot read while not connected"); q->setSocketError(QBluetoothSocket::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::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 = QAndroidJniObject(); if (inputThread) { // deleted already above (client->deleteLater()) inputThread = 0; } } q->setOpenMode(QIODevice::NotOpen); q->setSocketState(QBluetoothSocket::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 QAndroidJniObject &socket, QBluetoothServiceInfo::Protocol socketType_, QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode) { Q_Q(QBluetoothSocket); if (q->state() != QBluetoothSocket::UnconnectedState || !socket.isValid()) return false; if (!ensureNativeSocket(socketType_)) return false; socketObject = socket; QAndroidJniEnvironment env; inputStream = socketObject.callObjectMethod("getInputStream", "()Ljava/io/InputStream;"); outputStream = socketObject.callObjectMethod("getOutputStream", "()Ljava/io/OutputStream;"); if (env->ExceptionCheck() || !inputStream.isValid() || !outputStream.isValid()) { env->ExceptionDescribe(); env->ExceptionClear(); //close socket again socketObject.callMethod("close"); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } socketObject = inputStream = outputStream = remoteDevice = QAndroidJniObject(); errorString = QBluetoothSocket::tr("Obtaining streams for service failed"); q->setSocketError(QBluetoothSocket::NetworkError); q->setSocketState(QBluetoothSocket::UnconnectedState); return false; } remoteDevice = socketObject.callObjectMethod("getRemoteDevice", "()Landroid/bluetooth/BluetoothDevice;"); if (inputThread) { inputThread->deleteLater(); inputThread = 0; } inputThread = new InputStreamThread(this); QObject::connect(inputThread, SIGNAL(dataAvailable()), q, SIGNAL(readyRead()), Qt::QueuedConnection); QObject::connect(inputThread, SIGNAL(error(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, QAndroidJniObject(), !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 (QtAndroid::androidSdkVersion() < 23) return serviceUuid; if (serviceUuid.isNull()) return QBluetoothUuid(); bool isBaseUuid = false; serviceUuid.toUInt32(&isBaseUuid); if (isBaseUuid) return serviceUuid; const quint128 original = serviceUuid.toUInt128(); quint128 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