summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qbluetoothsocket_android.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qbluetoothsocket_android.cpp')
-rw-r--r--src/bluetooth/qbluetoothsocket_android.cpp451
1 files changed, 451 insertions, 0 deletions
diff --git a/src/bluetooth/qbluetoothsocket_android.cpp b/src/bluetooth/qbluetoothsocket_android.cpp
new file mode 100644
index 00000000..93089182
--- /dev/null
+++ b/src/bluetooth/qbluetoothsocket_android.cpp
@@ -0,0 +1,451 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Lauri Laanmets (Proekspert AS) <lauri.laanmets@eesti.ee>
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothsocket.h"
+#include "qbluetoothsocket_p.h"
+#include "qbluetoothaddress.h"
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QTime>
+#include <QtConcurrent/QtConcurrentRun>
+#include <QtAndroidExtras/QAndroidJniEnvironment>
+
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+QBluetoothSocketPrivate::QBluetoothSocketPrivate()
+ : socket(-1),
+ socketType(QBluetoothServiceInfo::UnknownProtocol),
+ state(QBluetoothSocket::UnconnectedState),
+ socketError(QBluetoothSocket::NoSocketError),
+ connecting(false),
+ discoveryAgent(0),
+ inputThread(0)
+{
+ adapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter",
+ "getDefaultAdapter",
+ "()Landroid/bluetooth/BluetoothAdapter;");
+ qRegisterMetaType<QBluetoothSocket::SocketError>("QBluetoothSocket::SocketError");
+ qRegisterMetaType<QBluetoothSocket::SocketState>("QBluetoothSocket::SocketState");
+}
+
+QBluetoothSocketPrivate::~QBluetoothSocketPrivate()
+{
+}
+
+bool QBluetoothSocketPrivate::ensureNativeSocket(QBluetoothServiceInfo::Protocol type)
+{
+ socketType = type;
+ if (socketType == QBluetoothServiceInfo::RfcommProtocol)
+ return true;
+
+ return false;
+}
+
+//TODO Convert uuid parameter to const reference (affects QNX too)
+void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, QBluetoothUuid uuid, QIODevice::OpenMode openMode)
+{
+ Q_Q(QBluetoothSocket);
+
+ q->setSocketState(QBluetoothSocket::ConnectingState);
+ QtConcurrent::run(this, &QBluetoothSocketPrivate::connectToServiceConc, address, uuid, openMode);
+}
+
+void QBluetoothSocketPrivate::connectToServiceConc(const QBluetoothAddress &address,
+ const QBluetoothUuid &uuid, QIODevice::OpenMode openMode)
+{
+ Q_Q(QBluetoothSocket);
+ Q_UNUSED(openMode);
+
+ qDebug() << "GGGGConnecting to" << address.toString() << uuid.toString();
+ 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>());
+
+ 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;
+ }
+
+ socketObject.callMethod<void>("connect");
+ if (env->ExceptionCheck() || socketObject.callMethod<jboolean>("isConnected") == JNI_FALSE) {
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ socketObject = remoteDevice = QAndroidJniObject();
+ errorString = QBluetoothSocket::tr("Connection to service failed");
+ q->setSocketError(QBluetoothSocket::ServiceNotFoundError);
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+ return;
+ }
+
+ if (inputThread) {
+ inputThread->stop();
+ inputThread->wait();
+ delete inputThread;
+ 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();
+
+ //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;
+ }
+
+ inputThread = new InputStreamThread(this);
+ QObject::connect(inputThread, SIGNAL(dataAvailable()), q, SIGNAL(readyRead()), Qt::QueuedConnection);
+ QObject::connect(inputThread, SIGNAL(error()),
+ this, SLOT(inputThreadError()), Qt::QueuedConnection);
+ inputThread->start();
+
+ q->setSocketState(QBluetoothSocket::ConnectedState);
+ emit q->connected();
+}
+
+void QBluetoothSocketPrivate::_q_writeNotify()
+{
+}
+
+void QBluetoothSocketPrivate::_q_readNotify()
+{
+}
+
+void QBluetoothSocketPrivate::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 QBluetoothSocketPrivate::inputThreadError() for details
+ */
+ //triggers abort of input thread as well
+ socketObject.callMethod<void>("close");
+ if (env->ExceptionCheck()) {
+
+ qCWarning(QT_BT_ANDROID) << "Error during closure of socket";
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ if (inputThread) {
+ inputThread->stop();
+ inputThread->wait();
+ delete inputThread;
+ inputThread = 0;
+ }
+
+ inputStream = outputStream = socketObject = remoteDevice = QAndroidJniObject();
+ }
+}
+
+QString QBluetoothSocketPrivate::localName() const
+{
+ if (adapter.isValid())
+ return adapter.callObjectMethod<jstring>("getName").toString();
+
+ return QString();
+}
+
+QBluetoothAddress QBluetoothSocketPrivate::localAddress() const
+{
+ QString result;
+ if (adapter.isValid())
+ result = adapter.callObjectMethod("getAddress", "()Ljava/lang/String;").toString();
+
+ return QBluetoothAddress(result);
+}
+
+quint16 QBluetoothSocketPrivate::localPort() const
+{
+ // Impossible to get channel number with current Android API (Levels 5 to 19)
+ return 0;
+}
+
+QString QBluetoothSocketPrivate::peerName() const
+{
+ if (!remoteDevice.isValid())
+ return QString();
+
+ return remoteDevice.callObjectMethod("getName", "()Ljava/lang/String;").toString();
+}
+
+QBluetoothAddress QBluetoothSocketPrivate::peerAddress() const
+{
+ if (!remoteDevice.isValid())
+ return QBluetoothAddress();
+
+ const QString address = remoteDevice.callObjectMethod("getAddress",
+ "()Ljava/lang/String;").toString();
+
+ return QBluetoothAddress(address);
+}
+
+quint16 QBluetoothSocketPrivate::peerPort() const
+{
+ // Impossible to get channel number with current Android API (Levels 5 to 13)
+ return 0;
+}
+
+qint64 QBluetoothSocketPrivate::writeData(const char *data, qint64 maxSize)
+{
+ //TODO implement buffered behavior (so far only unbuffered)
+ //TODO check that readData and writeData return -1 on error (on all platforms)
+ Q_Q(QBluetoothSocket);
+ if (state != QBluetoothSocket::ConnectedState || !outputStream.isValid()) {
+ qCWarning(QT_BT_ANDROID) << "Socket::writeData: " << (int)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;
+ }
+
+ return maxSize;
+}
+
+qint64 QBluetoothSocketPrivate::readData(char *data, qint64 maxSize)
+{
+ Q_Q(QBluetoothSocket);
+ if (state != QBluetoothSocket::ConnectedState || !inputThread) {
+ qCWarning(QT_BT_ANDROID) << "Socket::writeData: " << (int)state << outputStream.isValid() ;
+ errorString = QBluetoothSocket::tr("Cannot write while not connected");
+ q->setSocketError(QBluetoothSocket::OperationError);
+ return -1;
+ }
+
+ return inputThread->readData(data, maxSize);
+}
+
+void QBluetoothSocketPrivate::inputThreadError()
+{
+ Q_Q(QBluetoothSocket);
+
+ //any error from InputThread is a NetworkError
+ errorString = QBluetoothSocket::tr("Network error during read");
+ q->setSocketError(QBluetoothSocket::NetworkError);
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+ emit q->disconnected();
+}
+
+void QBluetoothSocketPrivate::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 QBluetoothSocketPrivate::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 QBluetoothSocketPrivate::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;
+ }
+
+ if (inputThread) {
+ inputThread->stop();
+ inputThread->wait();
+ delete inputThread;
+ inputThread = 0;
+ }
+ inputThread = new InputStreamThread(this);
+ QObject::connect(inputThread, SIGNAL(dataAvailable()), q, SIGNAL(readyRead()), Qt::QueuedConnection);
+ QObject::connect(inputThread, SIGNAL(error()),
+ this, SLOT(inputThreadError()), Qt::QueuedConnection);
+ inputThread->start();
+
+
+ q->setSocketState(socketState);
+ q->setOpenMode(openMode);
+
+ if (openMode == QBluetoothSocket::ConnectedState)
+ emit q->connected();
+
+ return true;
+}
+
+int QBluetoothSocketPrivate::socketDescriptor() const
+{
+ return 0;
+}
+
+qint64 QBluetoothSocketPrivate::bytesAvailable() const
+{
+ //We cannot access buffer directly as it is part of different thread
+ if (inputThread)
+ return inputThread->bytesAvailable();
+
+ return 0;
+}
+
+QT_END_NAMESPACE