blob: 85da325b82cc0655802882d0192a766deac4dcd5 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 Lauri Laanmets (Proekspert AS) <lauri.laanmets@eesti.ee>
** 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 <QtCore/QLoggingCategory>
#include <QtCore/QThread>
#include <QtCore/QTime>
#include <QtCore/private/qjni_p.h>
#include <QtAndroidExtras/QAndroidJniEnvironment>
#include <QtAndroid>
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<QAndroidJniObject>();
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<void>("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<void>("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<SocketConnectWorker> workerPointer;
};
QBluetoothSocketPrivateAndroid::QBluetoothSocketPrivateAndroid()
:
inputThread(0)
{
secFlags = QBluetooth::Secure;
adapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter",
"getDefaultAdapter",
"()Landroid/bluetooth/BluetoothAdapter;");
qRegisterMetaType<QBluetoothSocket::SocketError>();
qRegisterMetaType<QBluetoothSocket::SocketState>();
}
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<jobject>());
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<jint>("getServiceChannel",
"(Landroid/os/ParcelUuid;)I",
parcelUuid.object());
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
} else {
if (socketChannel
== remoteDevice.getStaticField<jint>("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<jstring>(),
paramTypes.object<jobjectArray>());
} 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<jstring>(),
paramTypes.object<jobjectArray>());
}
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<jobject>());
QAndroidJniObject invokeResult = method.callObjectMethod("invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;",
remoteDevice.object<jobject>(), 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<jstring>());
if (secFlags == QBluetooth::NoSecurity) {
qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm";
socketObject = remoteDevice.callObjectMethod("createInsecureRfcommSocketToServiceRecord",
"(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;",
uuidObject.object<jobject>());
} else {
qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm";
socketObject = remoteDevice.callObjectMethod("createRfcommSocketToServiceRecord",
"(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;",
uuidObject.object<jobject>());
}
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<jint>("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<jstring>());
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<jstring>());
if (secFlags == QBluetooth::NoSecurity) {
qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm";
socketObject = remoteDevice.callObjectMethod("createInsecureRfcommSocketToServiceRecord",
"(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;",
uuidObject.object<jobject>());
} else {
qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm";
socketObject = remoteDevice.callObjectMethod("createRfcommSocketToServiceRecord",
"(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;",
uuidObject.object<jobject>());
}
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<jstring>("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<const jbyte*>(data));
outputStream.callMethod<void>("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<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 = 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<void>("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 <qbluetoothsocket_android.moc>