summaryrefslogtreecommitdiffstats
path: root/src/bluetooth
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@digia.com>2014-02-10 15:37:17 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2014-02-13 09:47:27 +0100
commit173d16efb54ccc152f19afb9b1c2a87915fb414b (patch)
treef07ce85ba2cb973e3c08f3ed84252d92ee1c16de /src/bluetooth
parentdd75b1f776695006ca96fd43f995c3ba0549b968 (diff)
Port QtBluetooth to Android
This is a feature merge to dev targeting Qt 5.3. Known issues: -QTBUG-36754: QBluetoothServer::close() crashes -QTBUG-36763: QBluetothTransferManager port to Android not possible -QTBUG-36764: Improve QBluetoothLocalDevice::connectedDevices() -QTBUG-36810: Remove direct use of Android action strings The above issues and some other minor TODO's will be addressed until final release time. Task-number: QTBUG-33792 [ChangeLog][QtBluetooth][Android] QtBluetooth has been ported to Android. Change-Id: I31ba83e3b7d6aa68e7258b7e43235de7d1a6e68a Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com> Reviewed-by: Alex Blasche <alexander.blasche@digia.com>
Diffstat (limited to 'src/bluetooth')
-rw-r--r--src/bluetooth/android/android.pri17
-rw-r--r--src/bluetooth/android/androidbroadcastreceiver.cpp109
-rw-r--r--src/bluetooth/android/androidbroadcastreceiver_p.h77
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver.cpp132
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver_p.h66
-rw-r--r--src/bluetooth/android/inputstreamthread.cpp150
-rw-r--r--src/bluetooth/android/inputstreamthread_p.h78
-rw-r--r--src/bluetooth/android/jni_android.cpp111
-rw-r--r--src/bluetooth/android/localdevicebroadcastreceiver.cpp211
-rw-r--r--src/bluetooth/android/localdevicebroadcastreceiver_p.h72
-rw-r--r--src/bluetooth/android/serveracceptancethread.cpp235
-rw-r--r--src/bluetooth/android/serveracceptancethread_p.h92
-rw-r--r--src/bluetooth/android/servicediscoverybroadcastreceiver.cpp115
-rw-r--r--src/bluetooth/android/servicediscoverybroadcastreceiver_p.h68
-rw-r--r--src/bluetooth/bluetooth.pro21
-rw-r--r--src/bluetooth/qbluetooth.cpp1
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp205
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_p.h27
-rw-r--r--src/bluetooth/qbluetoothlocaldevice.cpp39
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_android.cpp443
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_p.h48
-rw-r--r--src/bluetooth/qbluetoothserver.cpp26
-rw-r--r--src/bluetooth/qbluetoothserver.h2
-rw-r--r--src/bluetooth/qbluetoothserver_android.cpp274
-rw-r--r--src/bluetooth/qbluetoothserver_bluez.cpp2
-rw-r--r--src/bluetooth/qbluetoothserver_p.h17
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent.cpp2
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent.h6
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_android.cpp513
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp4
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_p.h26
-rw-r--r--src/bluetooth/qbluetoothserviceinfo_android.cpp140
-rw-r--r--src/bluetooth/qbluetoothsocket.cpp97
-rw-r--r--src/bluetooth/qbluetoothsocket.h12
-rw-r--r--src/bluetooth/qbluetoothsocket_android.cpp451
-rw-r--r--src/bluetooth/qbluetoothsocket_p.h33
-rw-r--r--src/bluetooth/qbluetoothsocket_qnx.cpp7
-rw-r--r--src/bluetooth/qbluetoothtransfermanager.cpp4
38 files changed, 3870 insertions, 63 deletions
diff --git a/src/bluetooth/android/android.pri b/src/bluetooth/android/android.pri
new file mode 100644
index 00000000..3c8a0380
--- /dev/null
+++ b/src/bluetooth/android/android.pri
@@ -0,0 +1,17 @@
+PRIVATE_HEADERS += \
+ android/inputstreamthread_p.h \
+ android/devicediscoverybroadcastreceiver_p.h \
+ android/servicediscoverybroadcastreceiver_p.h \
+ android/androidbroadcastreceiver_p.h \
+ android/localdevicebroadcastreceiver_p.h \
+ android/serveracceptancethread_p.h
+
+
+SOURCES += \
+ android/inputstreamthread.cpp \
+ android/devicediscoverybroadcastreceiver.cpp \
+ android/servicediscoverybroadcastreceiver.cpp \
+ android/jni_android.cpp \
+ android/androidbroadcastreceiver.cpp \
+ android/localdevicebroadcastreceiver.cpp \
+ android/serveracceptancethread.cpp
diff --git a/src/bluetooth/android/androidbroadcastreceiver.cpp b/src/bluetooth/android/androidbroadcastreceiver.cpp
new file mode 100644
index 00000000..affa7683
--- /dev/null
+++ b/src/bluetooth/android/androidbroadcastreceiver.cpp
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** 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 <android/log.h>
+#include "android/androidbroadcastreceiver_p.h"
+#include <QtCore/QLoggingCategory>
+#include <QtCore/private/qjnihelpers_p.h>
+#include <QtGui/QGuiApplication>
+#include <QtAndroidExtras/QAndroidJniEnvironment>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+
+AndroidBroadcastReceiver::AndroidBroadcastReceiver(QObject* parent)
+ : QObject(parent), valid(false)
+{
+ //get QtActivity
+ activityObject = QAndroidJniObject(QtAndroidPrivate::activity());
+
+ broadcastReceiverObject = QAndroidJniObject("org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver");
+ if (!broadcastReceiverObject.isValid())
+ return;
+ broadcastReceiverObject.setField<jlong>("qtObject", reinterpret_cast<long>(this));
+
+ intentFilterObject = QAndroidJniObject("android/content/IntentFilter");
+ if (!intentFilterObject.isValid())
+ return;
+
+ valid = true;
+}
+
+AndroidBroadcastReceiver::~AndroidBroadcastReceiver()
+{
+ unregisterReceiver();
+}
+
+bool AndroidBroadcastReceiver::isValid() const
+{
+ return valid;
+}
+
+void AndroidBroadcastReceiver::unregisterReceiver()
+{
+ if (!valid)
+ return;
+
+ activityObject.callObjectMethod(
+ "unregisterReceiver",
+ "(Landroid/content/BroadcastReceiver;)V",
+ broadcastReceiverObject.object<jobject>());
+}
+
+void AndroidBroadcastReceiver::addAction(const QString &action)
+{
+ if (!valid)
+ return;
+
+ QAndroidJniObject actionString = QAndroidJniObject::fromString(action);
+ intentFilterObject.callMethod<void>("addAction", "(Ljava/lang/String;)V", actionString.object<jstring>());
+
+ activityObject.callObjectMethod(
+ "registerReceiver",
+ "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;",
+ broadcastReceiverObject.object<jobject>(),
+ intentFilterObject.object<jobject>());
+}
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/android/androidbroadcastreceiver_p.h b/src/bluetooth/android/androidbroadcastreceiver_p.h
new file mode 100644
index 00000000..baae6798
--- /dev/null
+++ b/src/bluetooth/android/androidbroadcastreceiver_p.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef JNIBROADCASTRECEIVER_H
+#define JNIBROADCASTRECEIVER_H
+#include <jni.h>
+#include <QtCore/QObject>
+#include <android/log.h>
+#include <QtAndroidExtras/QAndroidJniObject>
+
+QT_BEGIN_NAMESPACE
+
+void QtBroadcastReceiver_jniOnReceive(JNIEnv *, jobject, jlong, jobject, jobject);
+
+class AndroidBroadcastReceiver: public QObject
+{
+ Q_OBJECT
+public:
+ AndroidBroadcastReceiver(QObject* parent = 0);
+ virtual ~AndroidBroadcastReceiver();
+
+ void addAction(const QString &filter);
+ bool isValid() const;
+
+protected:
+ friend void QtBroadcastReceiver_jniOnReceive(JNIEnv *, jobject, jlong, jobject, jobject);
+ virtual void onReceive(JNIEnv *env, jobject context, jobject intent) = 0;
+
+ void unregisterReceiver();
+
+ QAndroidJniObject activityObject;
+ QAndroidJniObject intentFilterObject;
+ QAndroidJniObject broadcastReceiverObject;
+ bool valid;
+};
+
+QT_END_NAMESPACE
+#endif // JNIBROADCASTRECEIVER_H
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
new file mode 100644
index 00000000..16a6afe4
--- /dev/null
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** 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 "android/devicediscoverybroadcastreceiver_p.h"
+#include <QtCore/QLoggingCategory>
+#include <QtBluetooth/QBluetoothAddress>
+#include <QtBluetooth/QBluetoothDeviceInfo>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+DeviceDiscoveryBroadcastReceiver::DeviceDiscoveryBroadcastReceiver(QObject* parent): AndroidBroadcastReceiver(parent)
+{
+ addAction(QStringLiteral("android.bluetooth.device.action.FOUND"));
+ addAction(QStringLiteral("android.bluetooth.adapter.action.DISCOVERY_STARTED"));
+ addAction(QStringLiteral("android.bluetooth.adapter.action.DISCOVERY_FINISHED"));
+}
+
+void DeviceDiscoveryBroadcastReceiver::onReceive(JNIEnv *env, jobject context, jobject intent)
+{
+ Q_UNUSED(context);
+ Q_UNUSED(env);
+
+ QAndroidJniObject intentObject(intent);
+ const QString action = intentObject.callObjectMethod("getAction", "()Ljava/lang/String;").toString();
+
+ qCDebug(QT_BT_ANDROID) << "DeviceDiscoveryBroadcastReceiver::onReceive() - event:" << action;
+
+ if (action == QStringLiteral("android.bluetooth.adapter.action.DISCOVERY_FINISHED") ) {
+ emit finished();
+ } else if (action == QStringLiteral("android.bluetooth.adapter.action.DISCOVERY_STARTED") ) {
+
+ } else if (action == QStringLiteral("android.bluetooth.device.action.FOUND")) {
+ //get BluetoothDevice
+ QAndroidJniObject keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.DEVICE"));
+ QAndroidJniObject bluetoothDevice =
+ intentObject.callObjectMethod("getParcelableExtra",
+ "(Ljava/lang/String;)Landroid/os/Parcelable;",
+ keyExtra.object<jstring>());
+
+ if (!bluetoothDevice.isValid())
+ return;
+
+ const QString deviceName = bluetoothDevice.callObjectMethod<jstring>("getName").toString();
+ const QBluetoothAddress deviceAddress(bluetoothDevice.callObjectMethod<jstring>("getAddress").toString());
+ keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.RSSI"));
+ int rssi = intentObject.callMethod<jshort>("getShortExtra",
+ "(Ljava/lang/String;S)S",
+ keyExtra.object<jstring>(),
+ 0);
+ QAndroidJniObject bluetoothClass = bluetoothDevice.callObjectMethod("getBluetoothClass",
+ "()Landroid/bluetooth/BluetoothClass;");
+ if (!bluetoothClass.isValid())
+ return;
+ int classType = bluetoothClass.callMethod<jint>("getDeviceClass");
+
+
+ static QList<qint32> services;
+ if (services.count() == 0)
+ services << QBluetoothDeviceInfo::PositioningService
+ << QBluetoothDeviceInfo::NetworkingService
+ << QBluetoothDeviceInfo::RenderingService
+ << QBluetoothDeviceInfo::CapturingService
+ << QBluetoothDeviceInfo::ObjectTransferService
+ << QBluetoothDeviceInfo::AudioService
+ << QBluetoothDeviceInfo::TelephonyService
+ << QBluetoothDeviceInfo::InformationService;
+
+ //Matching BluetoothClass.Service values
+ qint32 result = 0;
+ qint32 current = 0;
+ for (int i = 0; i < services.count(); i++) {
+ current = services.at(i);
+ int id = (current << 16);
+ if (bluetoothClass.callMethod<jboolean>("hasService", "(I)Z", id))
+ result |= current;
+ }
+
+ result = result << 13;
+ classType |= result;
+
+ QBluetoothDeviceInfo info(deviceAddress, deviceName, classType);
+ info.setRssi(rssi);
+
+ emit deviceDiscovered(info);
+ }
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h b/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h
new file mode 100644
index 00000000..6c08b22e
--- /dev/null
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef DEVICEDISCOVERYBROADCASTRECEIVER_H
+#define DEVICEDISCOVERYBROADCASTRECEIVER_H
+
+#include "android/androidbroadcastreceiver_p.h"
+#include <QtBluetooth/QBluetoothDeviceDiscoveryAgent>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothDeviceInfo;
+
+class DeviceDiscoveryBroadcastReceiver : public AndroidBroadcastReceiver
+{
+ Q_OBJECT
+public:
+ DeviceDiscoveryBroadcastReceiver(QObject* parent = 0);
+ virtual void onReceive(JNIEnv *env, jobject context, jobject intent);
+
+signals:
+ void deviceDiscovered(const QBluetoothDeviceInfo &info);
+ void finished();
+};
+
+QT_END_NAMESPACE
+#endif // DEVICEDISCOVERYBROADCASTRECEIVER_H
diff --git a/src/bluetooth/android/inputstreamthread.cpp b/src/bluetooth/android/inputstreamthread.cpp
new file mode 100644
index 00000000..df32ee62
--- /dev/null
+++ b/src/bluetooth/android/inputstreamthread.cpp
@@ -0,0 +1,150 @@
+/****************************************************************************
+**
+** 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 <QtAndroidExtras/QAndroidJniEnvironment>
+
+#include "android/inputstreamthread_p.h"
+#include "qbluetoothsocket_p.h"
+
+QT_BEGIN_NAMESPACE
+
+
+InputStreamThread::InputStreamThread(QBluetoothSocketPrivate *socket)
+ : QThread(), m_stop(false)
+{
+ m_socket_p = socket;
+}
+
+void InputStreamThread::run()
+{
+ qint32 byte;
+ Q_UNUSED(byte)
+ while (1) {
+ {
+ QMutexLocker locker(&m_mutex);
+ if (m_stop)
+ break;
+ }
+ readFromInputStream();
+ }
+
+ QAndroidJniEnvironment env;
+ if (m_socket_p->inputStream.isValid())
+ m_socket_p->inputStream.callMethod<void>("close");
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+}
+
+bool InputStreamThread::bytesAvailable() const
+{
+ QMutexLocker locker(&m_mutex);
+ return m_socket_p->buffer.size();
+}
+
+//This runs inside the thread.
+void InputStreamThread::readFromInputStream()
+{
+ QAndroidJniEnvironment env;
+
+ int bufLen = 1000; // Seems to magical number that also low-end products can survive.
+ jbyteArray nativeArray = env->NewByteArray(bufLen);
+
+
+ jint ret = m_socket_p->inputStream.callMethod<jint>("read", "([BII)I", nativeArray, 0, bufLen);
+
+ if (env->ExceptionCheck() || ret < 0) {
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ env->DeleteLocalRef(nativeArray);
+ QMutexLocker lock(&m_mutex);
+ m_stop = true;
+
+ /*
+ * We cannot distinguish IOException due to valid closure or due to other error
+ * Therefore we always have to throw an error and a disconnect signal
+ * A genuine disconnect wouldn't need the error signal.
+ * For now we always signal error which implicitly emits disconnect() too.
+ */
+
+ emit error();
+ return;
+ }
+
+ if (ret == 0) {
+ qDebug() << "Nothing to read";
+ env->DeleteLocalRef(nativeArray);
+ return;
+ }
+
+ QMutexLocker lock(&m_mutex);
+ char *writePtr = m_socket_p->buffer.reserve(bufLen);
+ env->GetByteArrayRegion(nativeArray, 0, ret, reinterpret_cast<jbyte*>(writePtr));
+ env->DeleteLocalRef(nativeArray);
+ m_socket_p->buffer.chop(bufLen - ret);
+ emit dataAvailable();
+}
+
+void InputStreamThread::stop()
+{
+ QMutexLocker locker(&m_mutex);
+ m_stop = true;
+}
+
+qint64 InputStreamThread::readData(char *data, qint64 maxSize)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (m_stop)
+ return -1;
+
+ if (!m_socket_p->buffer.isEmpty())
+ return m_socket_p->buffer.read(data, maxSize);
+
+ return 0;
+}
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/android/inputstreamthread_p.h b/src/bluetooth/android/inputstreamthread_p.h
new file mode 100644
index 00000000..85852534
--- /dev/null
+++ b/src/bluetooth/android/inputstreamthread_p.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef INPUTSTREAMTHREAD_H
+#define INPUTSTREAMTHREAD_H
+
+#include <QtCore/QThread>
+#include <QtCore/QMutex>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothSocketPrivate;
+
+class InputStreamThread : public QThread
+{
+ Q_OBJECT
+public:
+ explicit InputStreamThread(QBluetoothSocketPrivate *socket_p);
+ virtual void run();
+
+ bool bytesAvailable() const;
+ void stop();
+
+ qint64 readData(char *data, qint64 maxSize);
+signals:
+ void dataAvailable();
+ void error();
+
+private:
+ void readFromInputStream();
+
+ QBluetoothSocketPrivate *m_socket_p;
+ mutable QMutex m_mutex;
+ bool m_stop;
+};
+
+QT_END_NAMESPACE
+
+#endif // INPUTSTREAMTHREAD_H
diff --git a/src/bluetooth/android/jni_android.cpp b/src/bluetooth/android/jni_android.cpp
new file mode 100644
index 00000000..eb1fc2dd
--- /dev/null
+++ b/src/bluetooth/android/jni_android.cpp
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** 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 <jni.h>
+#include <android/log.h>
+#include <QtCore/QLoggingCategory>
+#include <QtBluetooth/qbluetoothglobal.h>
+#include <QtAndroidExtras/QAndroidJniObject>
+#include "android/androidbroadcastreceiver_p.h"
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+void QtBroadcastReceiver_jniOnReceive(JNIEnv *env, jobject /*javaObject*/,
+ jlong qtObject, jobject context, jobject intent)
+{
+ reinterpret_cast<AndroidBroadcastReceiver*>(qtObject)->onReceive(env, context, intent);
+}
+
+static JNINativeMethod methods[] = {
+ {"jniOnReceive", "(JLandroid/content/Context;Landroid/content/Intent;)V",
+ (void *) QtBroadcastReceiver_jniOnReceive},
+};
+
+static const char logTag[] = "QtBluetooth";
+static const char classErrorMsg[] = "Can't find class \"%s\"";
+
+#define FIND_AND_CHECK_CLASS(CLASS_NAME) \
+clazz = env->FindClass(CLASS_NAME); \
+if (!clazz) { \
+ __android_log_print(ANDROID_LOG_FATAL, logTag, classErrorMsg, CLASS_NAME); \
+ return JNI_FALSE; \
+}
+
+static bool registerNatives(JNIEnv *env)
+{
+ jclass clazz;
+ FIND_AND_CHECK_CLASS("org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver");
+
+ if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ __android_log_print(ANDROID_LOG_FATAL, logTag, "RegisterNatives failed");
+ return false;
+ }
+
+ return true;
+}
+
+Q_BLUETOOTH_EXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
+{
+ typedef union {
+ JNIEnv *nativeEnvironment;
+ void *venv;
+ } UnionJNIEnvToVoid;
+
+ UnionJNIEnvToVoid uenv;
+ uenv.venv = 0;
+
+ if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
+ __android_log_print(ANDROID_LOG_FATAL, logTag, "GetEnv failed");
+ return -1;
+ }
+
+ JNIEnv *env = uenv.nativeEnvironment;
+ if (!registerNatives(env)) {
+ __android_log_print(ANDROID_LOG_FATAL, logTag, "registerNatives failed");
+ return -1;
+ }
+
+ if (QT_BT_ANDROID().isDebugEnabled())
+ __android_log_print(ANDROID_LOG_INFO, logTag, "Bluetooth start");
+
+ return JNI_VERSION_1_4;
+}
diff --git a/src/bluetooth/android/localdevicebroadcastreceiver.cpp b/src/bluetooth/android/localdevicebroadcastreceiver.cpp
new file mode 100644
index 00000000..0e81ef22
--- /dev/null
+++ b/src/bluetooth/android/localdevicebroadcastreceiver.cpp
@@ -0,0 +1,211 @@
+/****************************************************************************
+**
+** 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 <QtCore/QLoggingCategory>
+#include "localdevicebroadcastreceiver_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+LocalDeviceBroadcastReceiver::LocalDeviceBroadcastReceiver(QObject *parent) :
+ AndroidBroadcastReceiver(parent), previousScanMode(0)
+{
+ addAction(QStringLiteral("android.bluetooth.device.action.BOND_STATE_CHANGED"));
+ addAction(QStringLiteral("android.bluetooth.adapter.action.SCAN_MODE_CHANGED"));
+ addAction(QStringLiteral("android.bluetooth.device.action.ACL_CONNECTED"));
+ addAction(QStringLiteral("android.bluetooth.device.action.ACL_DISCONNECTED"));
+ addAction(QStringLiteral("android.bluetooth.device.action.PAIRING_REQUEST")); //API 19
+
+}
+
+void LocalDeviceBroadcastReceiver::onReceive(JNIEnv *env, jobject context, jobject intent)
+{
+ Q_UNUSED(context);
+ Q_UNUSED(env);
+
+ QAndroidJniObject intentObject(intent);
+ const QString action = intentObject.callObjectMethod("getAction", "()Ljava/lang/String;").toString();
+ qCDebug(QT_BT_ANDROID) << QStringLiteral("LocalDeviceBroadcastReceiver::onReceive() - event: %1").arg(action);
+
+ if (action == QStringLiteral("android.bluetooth.adapter.action.SCAN_MODE_CHANGED")) {
+ QAndroidJniObject extrasBundle =
+ intentObject.callObjectMethod("getExtras","()Landroid/os/Bundle;");
+ QAndroidJniObject keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.adapter.extra.SCAN_MODE"));
+
+ int extra = extrasBundle.callMethod<jint>("getInt",
+ "(Ljava/lang/String;)I",
+ keyExtra.object<jstring>());
+
+ if (previousScanMode != extra) {
+ previousScanMode = extra;
+
+ switch (extra) {
+ case 20: //BluetoothAdapter.SCAN_MODE_NONE
+ emit hostModeStateChanged(QBluetoothLocalDevice::HostPoweredOff);
+ break;
+ case 21: //BluetoothAdapter.SCAN_MODE_CONNECTABLE
+ emit hostModeStateChanged(QBluetoothLocalDevice::HostConnectable);
+ break;
+ case 23: //BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
+ emit hostModeStateChanged(QBluetoothLocalDevice::HostDiscoverable);
+ break;
+ default:
+ qCWarning(QT_BT_ANDROID) << "Unknown Host State";
+ break;
+ }
+ }
+ } else if (action == QStringLiteral("android.bluetooth.device.action.BOND_STATE_CHANGED")) {
+ //get BluetoothDevice
+ QAndroidJniObject keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.DEVICE"));
+ QAndroidJniObject bluetoothDevice =
+ intentObject.callObjectMethod("getParcelableExtra",
+ "(Ljava/lang/String;)Landroid/os/Parcelable;",
+ keyExtra.object<jstring>());
+
+ //get new bond state
+ keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.BOND_STATE"));
+ QAndroidJniObject extrasBundle =
+ intentObject.callObjectMethod("getExtras","()Landroid/os/Bundle;");
+ int bondState = extrasBundle.callMethod<jint>("getInt",
+ "(Ljava/lang/String;)I",
+ keyExtra.object<jstring>());
+
+ QBluetoothAddress address(bluetoothDevice.callObjectMethod<jstring>("getAddress").toString());
+ if (address.isNull())
+ return;
+
+ switch (bondState) {
+ case 10: //BluetoothDevice.BOND_NONE
+ emit pairingStateChanged(address, QBluetoothLocalDevice::Unpaired);
+ break;
+ case 11: //BluetoothDevice.BOND_BONDING
+ //we ignore this as Qt doesn't have equivalent API.
+ break;
+ case 12: //BluetoothDevice.BOND_BONDED
+ emit pairingStateChanged(address, QBluetoothLocalDevice::Paired);
+ break;
+ default:
+ qCWarning(QT_BT_ANDROID) << "Unknown BOND_STATE_CHANGED value:" << bondState;
+ break;
+ }
+ } else if (action == QStringLiteral("android.bluetooth.device.action.ACL_DISCONNECTED") ||
+ action == QStringLiteral("android.bluetooth.device.action.ACL_CONNECTED")) {
+
+ const bool isConnectEvent =
+ action == QStringLiteral("android.bluetooth.device.action.ACL_CONNECTED") ? true : false;
+
+ //get BluetoothDevice
+ QAndroidJniObject keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.DEVICE"));
+ QAndroidJniObject bluetoothDevice =
+ intentObject.callObjectMethod("getParcelableExtra",
+ "(Ljava/lang/String;)Landroid/os/Parcelable;",
+ keyExtra.object<jstring>());
+
+ QBluetoothAddress address(bluetoothDevice.callObjectMethod<jstring>("getAddress").toString());
+ if (address.isNull())
+ return;
+
+ emit connectDeviceChanges(address, isConnectEvent);
+ } else if (action == QStringLiteral("android.bluetooth.device.action.PAIRING_REQUEST")) {
+
+ QAndroidJniObject keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.PAIRING_VARIANT"));
+ int variant = intentObject.callMethod<jint>("getIntExtra",
+ "(Ljava/lang/String;I)I",
+ keyExtra.object<jstring>(),
+ -1);
+
+ int key = -1;
+ switch (variant) {
+ case -1: //ignore -> no pairing variant set
+ return;
+ case 2: //BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION
+ {
+ keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.PAIRING_KEY"));
+ key = intentObject.callMethod<jint>("getIntExtra",
+ "(Ljava/lang/String;I)I",
+ keyExtra.object<jstring>(),
+ -1);
+ if (key == -1)
+ return;
+
+ keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.DEVICE"));
+ QAndroidJniObject bluetoothDevice =
+ intentObject.callObjectMethod("getParcelableExtra",
+ "(Ljava/lang/String;)Landroid/os/Parcelable;",
+ keyExtra.object<jstring>());
+
+ //we need to keep a reference around in case the user confirms later on
+ pairingDevice = bluetoothDevice;
+
+ QBluetoothAddress address(bluetoothDevice.callObjectMethod<jstring>("getAddress").toString());
+
+ //User has choice to confirm or not. If no confirmation is happening
+ //the OS default pairing dialog can be used or timeout occurs.
+ emit pairingDisplayConfirmation(address, QString::number(key));
+ break;
+ }
+ default:
+ qCWarning(QT_BT_ANDROID) << "Unknown pairing variant: " << variant;
+ return;
+ }
+ }
+}
+
+bool LocalDeviceBroadcastReceiver::pairingConfirmation(bool accept)
+{
+ if (!pairingDevice.isValid())
+ return false;
+
+ bool success = pairingDevice.callMethod<jboolean>("setPairingConfirmation",
+ "(Z)Z", accept);
+ pairingDevice = QAndroidJniObject();
+ return success;
+}
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/android/localdevicebroadcastreceiver_p.h b/src/bluetooth/android/localdevicebroadcastreceiver_p.h
new file mode 100644
index 00000000..ddd65d92
--- /dev/null
+++ b/src/bluetooth/android/localdevicebroadcastreceiver_p.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** 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 "android/androidbroadcastreceiver_p.h"
+#include <QtBluetooth/QBluetoothAddress>
+#include <QtBluetooth/QBluetoothLocalDevice>
+
+#ifndef LOCALDEVICEBROADCASTRECEIVER_H
+#define LOCALDEVICEBROADCASTRECEIVER_H
+
+QT_BEGIN_NAMESPACE
+
+class LocalDeviceBroadcastReceiver : public AndroidBroadcastReceiver
+{
+ Q_OBJECT
+public:
+ explicit LocalDeviceBroadcastReceiver(QObject *parent = 0);
+ virtual ~LocalDeviceBroadcastReceiver() {}
+ virtual void onReceive(JNIEnv *env, jobject context, jobject intent);
+ bool pairingConfirmation(bool accept);
+
+signals:
+ void hostModeStateChanged(QBluetoothLocalDevice::HostMode state);
+ void pairingStateChanged(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing);
+ void connectDeviceChanges(const QBluetoothAddress &address, bool isConnectEvent);
+ void pairingDisplayConfirmation(const QBluetoothAddress &address, const QString& pin);
+private:
+ int previousScanMode;
+ QAndroidJniObject pairingDevice;
+};
+
+QT_END_NAMESPACE
+
+#endif // LOCALDEVICEBROADCASTRECEIVER_H
diff --git a/src/bluetooth/android/serveracceptancethread.cpp b/src/bluetooth/android/serveracceptancethread.cpp
new file mode 100644
index 00000000..88a92478
--- /dev/null
+++ b/src/bluetooth/android/serveracceptancethread.cpp
@@ -0,0 +1,235 @@
+/****************************************************************************
+**
+** 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 <QtCore/QLoggingCategory>
+#include <QtAndroidExtras/QAndroidJniEnvironment>
+
+#include "android/serveracceptancethread_p.h"
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+ServerAcceptanceThread::ServerAcceptanceThread(QObject *parent) :
+ QThread(parent), m_stop(false), maxPendingConnections(1)
+{
+ btAdapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter",
+ "getDefaultAdapter",
+ "()Landroid/bluetooth/BluetoothAdapter;");
+}
+
+ServerAcceptanceThread::~ServerAcceptanceThread()
+{
+ Q_ASSERT(!isRunning());
+ QMutexLocker lock(&m_mutex);
+ shutdownPendingConnections();
+}
+
+void ServerAcceptanceThread::setServiceDetails(const QBluetoothUuid &uuid,
+ const QString &serviceName,
+ QBluetooth::SecurityFlags securityFlags)
+{
+ QMutexLocker lock(&m_mutex);
+ m_uuid = uuid;
+ m_serviceName = serviceName;
+ secFlags = securityFlags;
+}
+
+void ServerAcceptanceThread::run()
+{
+ m_mutex.lock();
+
+ qCDebug(QT_BT_ANDROID) << "Starting ServerSocketAccept thread";
+ if (!validSetup()) {
+ qCWarning(QT_BT_ANDROID) << "Invalid Server Socket setup";
+ m_mutex.unlock();
+ return;
+ }
+
+ shutdownPendingConnections();
+
+ m_stop = false;
+
+ QString tempUuid = m_uuid.toString();
+ tempUuid.chop(1); //remove trailing '}'
+ tempUuid.remove(0,1); //remove first '{'
+
+ QAndroidJniEnvironment env;
+ QAndroidJniObject inputString = QAndroidJniObject::fromString(tempUuid);
+ QAndroidJniObject uuidObject = QAndroidJniObject::callStaticObjectMethod(
+ "java/util/UUID", "fromString",
+ "(Ljava/lang/String;)Ljava/util/UUID;",
+ inputString.object<jstring>());
+ inputString = QAndroidJniObject::fromString(m_serviceName);
+ if (((int)secFlags) == 0) { //no form of security flag set
+ qCDebug(QT_BT_ANDROID) << "InSecure listening";
+ btServerSocket = btAdapter.callObjectMethod("listenUsingInsecureRfcommWithServiceRecord",
+ "(Ljava/lang/String;Ljava/util/UUID;)Landroid/bluetooth/BluetoothServerSocket;",
+ inputString.object<jstring>(),
+ uuidObject.object<jobject>());
+ } else {
+ qCDebug(QT_BT_ANDROID) << "Secure listening";
+ btServerSocket = btAdapter.callObjectMethod("listenUsingRfcommWithServiceRecord",
+ "(Ljava/lang/String;Ljava/util/UUID;)Landroid/bluetooth/BluetoothServerSocket;",
+ inputString.object<jstring>(),
+ uuidObject.object<jobject>());
+ }
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ qCWarning(QT_BT_ANDROID) << "Cannot setup rfcomm socket listener";
+ m_mutex.unlock();
+ return;
+ }
+
+ if (!btServerSocket.isValid()) {
+ qCWarning(QT_BT_ANDROID) << "Invalid BluetoothServerSocket";
+ m_mutex.unlock();
+ return;
+ }
+
+ while (!m_stop) {
+ m_mutex.unlock();
+
+ qCDebug(QT_BT_ANDROID) << "Waiting for new incoming socket";
+ QAndroidJniEnvironment env;
+
+ //this call blocks until we see an incoming connection
+ QAndroidJniObject socket = btServerSocket.callObjectMethod("accept",
+ "()Landroid/bluetooth/BluetoothSocket;");
+
+ qCDebug(QT_BT_ANDROID) << "New socket accepted: ->" << socket.isValid();
+ bool exceptionOccurred = false;
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ exceptionOccurred = true;
+ }
+
+ m_mutex.lock();
+
+ if (exceptionOccurred || m_stop) {
+ //if m_stop is true there is nothing really to be done but exit
+ m_stop = true;
+ } else if (socket.isValid()){
+ if (pendingSockets.count() < maxPendingConnections) {
+ pendingSockets.append(socket);
+ emit newConnection();
+ } else {
+ qCWarning(QT_BT_ANDROID) << "Refusing connection due to limited pending socket queue";
+ socket.callMethod<void>("close");
+ if (env->ExceptionCheck()) {
+ qCWarning(QT_BT_ANDROID) << "Error during refusal of new socket";
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+
+ }
+ } else {
+ //should never happen as invalid socket should cause exception
+ qCWarning(QT_BT_ANDROID) << "Invalid state during server socket accept";
+ }
+ }
+
+ m_uuid = QBluetoothUuid();
+ m_serviceName = QString();
+ btServerSocket = QAndroidJniObject();
+ m_mutex.unlock();
+}
+
+void ServerAcceptanceThread::stop()
+{
+ QMutexLocker lock(&m_mutex);
+ m_stop = true;
+
+ QAndroidJniEnvironment env;
+ if (btServerSocket.isValid()) {
+ qCDebug(QT_BT_ANDROID) << "Closing server socket";
+ btServerSocket.callMethod<void>("close");
+ if (env->ExceptionCheck()) {
+ qCWarning(QT_BT_ANDROID) << "Exception during closure of server socket";
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ qCDebug(QT_BT_ANDROID) << "Closing server socket111";
+ }
+}
+
+bool ServerAcceptanceThread::hasPendingConnections() const
+{
+ QMutexLocker lock(&m_mutex);
+ return (pendingSockets.count() > 0);
+}
+
+/*
+ * Returns the next pending connection or an invalid JNI object.
+ * Note that even a stopped thread may still have pending
+ * connections. Pending connections are only terminated upon
+ * thread restart or destruction.
+ */
+QAndroidJniObject ServerAcceptanceThread::nextPendingConnection()
+{
+ QMutexLocker lock(&m_mutex);
+ if (pendingSockets.isEmpty())
+ return QAndroidJniObject();
+ else
+ return pendingSockets.takeFirst();
+}
+
+void ServerAcceptanceThread::setMaxPendingConnections(int maximumCount)
+{
+ QMutexLocker lock(&m_mutex);
+ maxPendingConnections = maximumCount;
+}
+
+//must be run inside the lock but doesn't lock by itself
+bool ServerAcceptanceThread::validSetup() const
+{
+ return (!m_uuid.isNull() && !m_serviceName.isEmpty());
+}
+
+//must be run inside the lock but doesn't lock by itself
+void ServerAcceptanceThread::shutdownPendingConnections()
+{
+ while (!pendingSockets.isEmpty()) {
+ QAndroidJniObject socket = pendingSockets.takeFirst();
+ socket.callMethod<void>("close");
+ }
+}
diff --git a/src/bluetooth/android/serveracceptancethread_p.h b/src/bluetooth/android/serveracceptancethread_p.h
new file mode 100644
index 00000000..1297e48f
--- /dev/null
+++ b/src/bluetooth/android/serveracceptancethread_p.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef SERVERACCEPTANCETHREAD_H
+#define SERVERACCEPTANCETHREAD_H
+
+#include <QtCore/QMutex>
+#include <QtCore/QThread>
+#include <QtAndroidExtras/QAndroidJniObject>
+#include <QtBluetooth/QBluetoothUuid>
+#include "qbluetooth.h"
+
+
+class ServerAcceptanceThread : public QThread
+{
+ Q_OBJECT
+public:
+ enum AndroidError {
+ AndroidNoError
+ };
+
+ explicit ServerAcceptanceThread(QObject *parent = 0);
+ ~ServerAcceptanceThread();
+ void setServiceDetails(const QBluetoothUuid &uuid, const QString &serviceName,
+ QBluetooth::SecurityFlags securityFlags);
+ virtual void run();
+
+ void stop();
+ bool hasPendingConnections() const;
+ QAndroidJniObject nextPendingConnection();
+ void setMaxPendingConnections(int maximumCount);
+
+signals:
+ void newConnection();
+ void error(ServerAcceptanceThread::AndroidError);
+
+private:
+ bool validSetup() const;
+ void shutdownPendingConnections();
+
+ QList<QAndroidJniObject> pendingSockets;
+ QAndroidJniObject btAdapter;
+ QAndroidJniObject btServerSocket;
+ mutable QMutex m_mutex;
+ QString m_serviceName;
+ QBluetoothUuid m_uuid;
+ bool m_stop;
+ AndroidError lastError;
+ int maxPendingConnections;
+ QBluetooth::SecurityFlags secFlags;
+
+};
+
+#endif // SERVERACCEPTANCETHREAD_H
diff --git a/src/bluetooth/android/servicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/servicediscoverybroadcastreceiver.cpp
new file mode 100644
index 00000000..341617bc
--- /dev/null
+++ b/src/bluetooth/android/servicediscoverybroadcastreceiver.cpp
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** 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 "android/servicediscoverybroadcastreceiver_p.h"
+#include <QtCore/QLoggingCategory>
+#include <QtAndroidExtras/QAndroidJniEnvironment>
+#include <QtBluetooth/QBluetoothAddress>
+#include <QtBluetooth/QBluetoothDeviceInfo>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+ServiceDiscoveryBroadcastReceiver::ServiceDiscoveryBroadcastReceiver(QObject* parent): AndroidBroadcastReceiver(parent)
+{
+ addAction(QStringLiteral("android.bluetooth.device.action.UUID"));
+}
+
+void ServiceDiscoveryBroadcastReceiver::onReceive(JNIEnv *env, jobject context, jobject intent)
+{
+ Q_UNUSED(context);
+ Q_UNUSED(env);
+
+ QAndroidJniObject intentObject(intent);
+ const QString action = intentObject.callObjectMethod("getAction", "()Ljava/lang/String;").toString();
+
+ qCDebug(QT_BT_ANDROID) << "ServiceDiscoveryBroadcastReceiver::onReceive() - event:" << action;
+
+ if (action == QStringLiteral("android.bluetooth.device.action.UUID")) {
+ QAndroidJniObject keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.UUID"));
+ QAndroidJniObject parcelableUuids = intentObject.callObjectMethod(
+ "getParcelableArrayExtra",
+ "(Ljava/lang/String;)[Landroid/os/Parcelable;",
+ keyExtra.object<jstring>());
+ if (!parcelableUuids.isValid())
+ return;
+ QList<QBluetoothUuid> result = ServiceDiscoveryBroadcastReceiver::convertParcelableArray(parcelableUuids);
+
+ keyExtra = QAndroidJniObject::fromString(
+ QStringLiteral("android.bluetooth.device.extra.DEVICE"));
+ QAndroidJniObject bluetoothDevice =
+ intentObject.callObjectMethod("getParcelableExtra",
+ "(Ljava/lang/String;)Landroid/os/Parcelable;",
+ keyExtra.object<jstring>());
+ QBluetoothAddress address;
+ if (bluetoothDevice.isValid()) {
+ address = QBluetoothAddress(bluetoothDevice.callObjectMethod<jstring>("getAddress").toString());
+ emit uuidFetchFinished(address, result);
+ }
+ }
+}
+
+QList<QBluetoothUuid> ServiceDiscoveryBroadcastReceiver::convertParcelableArray(const QAndroidJniObject &parcelUuidArray)
+{
+ QList<QBluetoothUuid> result;
+ QAndroidJniEnvironment env;
+ QAndroidJniObject p;
+
+ jobjectArray parcels = parcelUuidArray.object<jobjectArray>();
+ if (!parcels)
+ return result;
+
+ jint size = env->GetArrayLength(parcels);
+ for (int i = 0; i < size; i++) {
+ p = env->GetObjectArrayElement(parcels, i);
+
+ QBluetoothUuid uuid(p.callObjectMethod<jstring>("toString").toString());
+ //qCDebug(QT_BT_ANDROID) << uuid.toString();
+ result.append(uuid);
+ }
+
+ return result;
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/bluetooth/android/servicediscoverybroadcastreceiver_p.h b/src/bluetooth/android/servicediscoverybroadcastreceiver_p.h
new file mode 100644
index 00000000..e75095e8
--- /dev/null
+++ b/src/bluetooth/android/servicediscoverybroadcastreceiver_p.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef SERVICEDISCOVERYBROADCASTRECEIVER_H
+#define SERVICEDISCOVERYBROADCASTRECEIVER_H
+
+#include "android/androidbroadcastreceiver_p.h"
+#include <QtCore/QList>
+#include <QtBluetooth/QBluetoothDeviceDiscoveryAgent>
+#include <QtBluetooth/QBluetoothUuid>
+
+QT_BEGIN_NAMESPACE
+
+class QBluetoothDeviceInfo;
+
+class ServiceDiscoveryBroadcastReceiver : public AndroidBroadcastReceiver
+{
+ Q_OBJECT
+public:
+ ServiceDiscoveryBroadcastReceiver(QObject* parent = 0);
+ virtual void onReceive(JNIEnv *env, jobject context, jobject intent);
+
+ static QList<QBluetoothUuid> convertParcelableArray(const QAndroidJniObject &obj);
+
+signals:
+ void uuidFetchFinished(const QBluetoothAddress &addr, const QList<QBluetoothUuid> &serviceUuid);
+};
+
+QT_END_NAMESPACE
+#endif // SERVICEDISCOVERYBROADCASTRECEIVER_H
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro
index d7214f2c..44d2444c 100644
--- a/src/bluetooth/bluetooth.pro
+++ b/src/bluetooth/bluetooth.pro
@@ -94,6 +94,27 @@ config_bluez:qtHaveModule(dbus) {
qbluetoothserver_qnx.cpp \
qbluetoothtransferreply_qnx.cpp
+} else:android:!android-no-sdk {
+ include(android/android.pri)
+ DEFINES += QT_ANDROID_BLUETOOTH
+ QT += core-private androidextras
+
+ ANDROID_PERMISSIONS = \
+ android.permission.BLUETOOTH \
+ android.permission.BLUETOOTH_ADMIN
+ ANDROID_BUNDLED_JAR_DEPENDENCIES = \
+ jar/QtAndroidBluetooth-bundled.jar:org.qtproject.qt5.android.bluetooth.QtBluetoothBroadcastReceiver
+ ANDROID_JAR_DEPENDENCIES = \
+ jar/QtAndroidBluetooth.jar:org.qtproject.qt5.android.bluetooth.QtBluetoothBroadcastReceiver
+
+ SOURCES += \
+ qbluetoothdevicediscoveryagent_android.cpp \
+ qbluetoothlocaldevice_android.cpp \
+ qbluetoothserviceinfo_android.cpp \
+ qbluetoothservicediscoveryagent_android.cpp \
+ qbluetoothsocket_android.cpp \
+ qbluetoothserver_android.cpp
+
} else {
message("Unsupported bluetooth platform, will not build a working QBluetooth library")
message("Either no Qt dBus found or no Bluez headers")
diff --git a/src/bluetooth/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp
index 29b14822..4ba14dba 100644
--- a/src/bluetooth/qbluetooth.cpp
+++ b/src/bluetooth/qbluetooth.cpp
@@ -78,6 +78,7 @@ namespace QBluetooth {
}
Q_LOGGING_CATEGORY(QT_BT, "qt.bluetooth")
+Q_LOGGING_CATEGORY(QT_BT_ANDROID, "qt.bluetooth.android")
Q_LOGGING_CATEGORY(QT_BT_BLUEZ, "qt.bluetooth.bluez")
Q_LOGGING_CATEGORY(QT_BT_QNX, "qt.bluetooth.qnx")
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
new file mode 100644
index 00000000..871e61e2
--- /dev/null
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_android.cpp
@@ -0,0 +1,205 @@
+/****************************************************************************
+**
+** 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 "qbluetoothdevicediscoveryagent_p.h"
+#include <QtCore/QLoggingCategory>
+#include <QtBluetooth/QBluetoothAddress>
+#include <QtBluetooth/QBluetoothDeviceInfo>
+#include "android/devicediscoverybroadcastreceiver_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate(
+ const QBluetoothAddress &deviceAdapter)
+ : inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry),
+ lastError(QBluetoothDeviceDiscoveryAgent::NoError), errorString(QStringLiteral()),
+ receiver(0),
+ m_adapterAddress(deviceAdapter),
+ m_active(false),
+ pendingCancel(false),
+ pendingStart(false)
+{
+ adapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter",
+ "getDefaultAdapter",
+ "()Landroid/bluetooth/BluetoothAdapter;");
+}
+
+QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate()
+{
+ if (m_active)
+ stop();
+
+ delete receiver;
+}
+
+bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const
+{
+ if (pendingStart)
+ return true;
+ if (pendingCancel)
+ return false;
+ return m_active;
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::start()
+{
+ if (pendingCancel) {
+ pendingStart = true;
+ return;
+ }
+
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+
+ if (!adapter.isValid()) {
+ qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth";
+ lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Device does not support Bluetooth.");
+ emit q->error(lastError);
+ return;
+ }
+
+ if (!m_adapterAddress.isNull() &&
+ adapter.callObjectMethod<jstring>("getAddress").toString() != m_adapterAddress.toString()) {
+ qCWarning(QT_BT_ANDROID) << "Incorrect local adapter passed.";
+ lastError = QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Passed address is not a local device.");
+ emit q->error(lastError);
+ return;
+ }
+
+ const int state = adapter.callMethod<jint>("getState");
+ if (state != 12 ) { //BluetoothAdapter.STATE_ON
+ lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off.");
+ emit q->error(lastError);
+ return;
+ }
+
+ //install Java BroadcastReceiver
+ if (!receiver) {
+ receiver = new DeviceDiscoveryBroadcastReceiver();
+ qRegisterMetaType<QBluetoothDeviceInfo>("QBluetoothDeviceInfo");
+ QObject::connect(receiver, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)),
+ this, SLOT(processDiscoveredDevices(const QBluetoothDeviceInfo&)));
+ QObject::connect(receiver, SIGNAL(finished()), this, SLOT(processDiscoveryFinished()));
+ }
+
+ discoveredDevices.clear();
+
+ const bool success = adapter.callMethod<jboolean>("startDiscovery");
+ if (!success) {
+ lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Discovery cannot be started.");
+ emit q->error(lastError);
+ return;
+ }
+
+ m_active = true;
+
+ qCDebug(QT_BT_ANDROID) << "QBluetoothDeviceDiscoveryAgentPrivate::start() - successfully executed.";
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::stop()
+{
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+
+ if (!m_active)
+ return;
+
+ pendingCancel = true;
+ pendingStart = false;
+ bool success = adapter.callMethod<jboolean>("cancelDiscovery");
+ if (!success) {
+ lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Discovery cannot be stopped.");
+ emit q->error(lastError);
+ return;
+ }
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::processDiscoveryFinished()
+{
+ //We need to guard because Android sends two DISCOVERY_FINISHED when cancelling
+ //Also if we have two active agents both receive the same signal.
+ //If this one is not active ignore the device information
+ if (!m_active)
+ return;
+
+ m_active = false;
+
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+
+ if (pendingCancel && !pendingStart) {
+ emit q->canceled();
+ pendingCancel = false;
+ } else if (pendingStart) {
+ pendingStart = pendingCancel = false;
+ start();
+ } else {
+ //check that it didn't finish due to turned off Bluetooth Device
+ const int state = adapter.callMethod<jint>("getState");
+ if (state != 12 ) { //BluetoothAdapter.STATE_ON
+ lastError = QBluetoothDeviceDiscoveryAgent::PoweredOffError;
+ errorString = QBluetoothDeviceDiscoveryAgent::tr("Device is powered off.");
+ emit q->error(lastError);
+ return;
+ }
+ emit q->finished();
+ }
+}
+
+void QBluetoothDeviceDiscoveryAgentPrivate::processDiscoveredDevices(const QBluetoothDeviceInfo &info)
+{
+ //If we have two active agents both receive the same signal.
+ // If this one is not active ignore the device information
+ if (!m_active)
+ return;
+
+ Q_Q(QBluetoothDeviceDiscoveryAgent);
+
+ discoveredDevices.append(info);
+ qCDebug(QT_BT_ANDROID) << "Device found: " << info.name() << info.address().toString();
+ emit q->deviceDiscovered(info);
+}
+QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
index 56a0c275..71dec409 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h
@@ -43,12 +43,15 @@
#define QBLUETOOTHDEVICEDISCOVERYAGENT_P_H
#include "qbluetoothdevicediscoveryagent.h"
+#ifdef QT_ANDROID_BLUETOOTH
+#include <QtAndroidExtras/QAndroidJniObject>
+#include "android/devicediscoverybroadcastreceiver_p.h"
+#endif
-#include <QVariantMap>
-
-#include <qbluetoothaddress.h>
+#include <QtCore/QVariantMap>
-#include <qbluetoothlocaldevice.h>
+#include <QtBluetooth/QBluetoothAddress>
+#include <QtBluetooth/QBluetoothLocalDevice>
#ifdef QT_BLUEZ_BLUETOOTH
class OrgBluezManagerInterface;
@@ -65,7 +68,7 @@ QT_END_NAMESPACE
QT_BEGIN_NAMESPACE
class QBluetoothDeviceDiscoveryAgentPrivate
-#if defined(QT_QNX_BLUETOOTH)
+#if defined(QT_QNX_BLUETOOTH) || defined(QT_ANDROID_BLUETOOTH)
: public QObject {
Q_OBJECT
#else
@@ -92,7 +95,19 @@ private:
QBluetoothDeviceDiscoveryAgent::Error lastError;
QString errorString;
-#ifdef QT_BLUEZ_BLUETOOTH
+#ifdef QT_ANDROID_BLUETOOTH
+private Q_SLOTS:
+ void processDiscoveryFinished();
+ void processDiscoveredDevices(const QBluetoothDeviceInfo& info);
+
+private:
+ DeviceDiscoveryBroadcastReceiver* receiver;
+ QBluetoothAddress m_adapterAddress;
+ bool m_active;
+ QAndroidJniObject adapter;
+
+ bool pendingCancel, pendingStart;
+#elif defined(QT_BLUEZ_BLUETOOTH)
QBluetoothAddress m_adapterAddress;
bool pendingCancel;
bool pendingStart;
diff --git a/src/bluetooth/qbluetoothlocaldevice.cpp b/src/bluetooth/qbluetoothlocaldevice.cpp
index fe13a2de..e299ab71 100644
--- a/src/bluetooth/qbluetoothlocaldevice.cpp
+++ b/src/bluetooth/qbluetoothlocaldevice.cpp
@@ -92,12 +92,13 @@ QT_BEGIN_NAMESPACE
if they have previously been paired with it or otherwise know its address. This powers up the
device if it was powered off.
\value HostDiscoverable Remote Bluetooth devices can discover the presence of the local
- Bluetooth device. The device will also be connectable, and powered on.
+ Bluetooth device. The device will also be connectable, and powered on. On Android, this mode can only be active
+ for a maximum of 5 minutes.
\value HostDiscoverableLimitedInquiry Remote Bluetooth devices can discover the presence of the local
Bluetooth device when performing a limited inquiry. This should be used for locating services that are
only made discoverable for a limited period of time. This can speed up discovery between gaming devices,
as service discovery can be skipped on devices not in LimitedInquiry mode. In this mode, the device will
- be connectable and powered on, if required.
+ be connectable and powered on, if required. This mode is is not supported on Android.
*/
@@ -190,21 +191,36 @@ bool QBluetoothLocalDevice::isValid() const
\fn QBluetoothLocalDevice::deviceConnected(const QBluetoothAddress &address)
\since 5.3
- A device with \a address is connected with this device.
+ This signal is emitted when the local device establishes a connection to a remote device
+ with \a address.
+
+ \sa deviceDisconnected(), connectedDevices()
*/
/*!
\fn QBluetoothLocalDevice::deviceDisconnected(const QBluetoothAddress &address)
\since 5.3
- A device with \a address is disconnected from this device.
+ This signal is emitted when the local device disconnects from a remote Bluetooth device
+ with \a address.
+
+ \sa deviceConnected(), connectedDevices()
*/
/*!
\fn QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
\since 5.3
- Returns the list of connected devices.
+ Returns the list of connected devices. This list is different from the list of currently
+ paired devices.
+
+ On Android it is not possible to retrieve a list of connected devices. It is only possible to
+ listen to (dis)connect changes. For convenience, this class monitors all connect
+ and disconnect events since its instanciation and returns the current list when calling this function.
+ Therefore it is possible that this function returns an empty list shortly after creating an
+ instance.
+
+ \sa deviceConnected(), deviceDisconnected()
*/
/*!
@@ -218,8 +234,12 @@ bool QBluetoothLocalDevice::isValid() const
\fn QBluetoothLocalDevice::pairingDisplayConfirmation(const QBluetoothAddress &address, QString pin)
Signal by some platforms to display a pairing confirmation dialog for \a address. The user
- is asked to confirm the \a pin is the same on both devices. QBluetoothLocalDevice::pairingConfirmation(bool)
+ is asked to confirm the \a pin is the same on both devices. The \l pairingConfirmation() function
must be called to indicate if the user accepts or rejects the displayed pin.
+
+ This signal is only emitted for pairing requests issues by calling \l requestPairing().
+
+ \sa pairingConfirmation()
*/
/*!
@@ -227,6 +247,8 @@ bool QBluetoothLocalDevice::isValid() const
To be called after getting a pairingDisplayConfirmation(). The \a accept parameter either
accepts the pairing or rejects it.
+
+ Accepting a pairing always refers to the last pairing request issued via \l requestPairing().
*/
/*!
@@ -234,6 +256,8 @@ bool QBluetoothLocalDevice::isValid() const
Signal by some platforms to display the \a pin to the user for \a address. The pin is automatically
generated, and does not need to be confirmed.
+
+ This signal is only emitted for pairing requests issues by calling \l requestPairing().
*/
/*!
@@ -249,7 +273,8 @@ bool QBluetoothLocalDevice::isValid() const
Pairing or unpairing has completed with \a address. Current pairing status is in \a pairing.
If the pairing request was not successful, this signal will not be emitted. The error() signal
- is emitted if the pairing request failed.
+ is emitted if the pairing request failed. The signal is only ever emitted for pairing requests
+ which have previously requested by calling \l requestPairing() of the current object instance.
*/
/*!
diff --git a/src/bluetooth/qbluetoothlocaldevice_android.cpp b/src/bluetooth/qbluetoothlocaldevice_android.cpp
new file mode 100644
index 00000000..4e441bc2
--- /dev/null
+++ b/src/bluetooth/qbluetoothlocaldevice_android.cpp
@@ -0,0 +1,443 @@
+/****************************************************************************
+**
+** 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 <QtCore/QLoggingCategory>
+#include <QtCore/private/qjnihelpers_p.h>
+#include <QtAndroidExtras/QAndroidJniEnvironment>
+#include <QtAndroidExtras/QAndroidJniObject>
+#include <QtBluetooth/QBluetoothLocalDevice>
+#include <QtBluetooth/QBluetoothAddress>
+
+#include "qbluetoothlocaldevice_p.h"
+#include "android/localdevicebroadcastreceiver_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(
+ QBluetoothLocalDevice *q, const QBluetoothAddress &address)
+ : q_ptr(q), obj(0), pendingHostModeTransition(false)
+{
+ initialize(address);
+
+ receiver = new LocalDeviceBroadcastReceiver(q_ptr);
+ QObject::connect(receiver, SIGNAL(hostModeStateChanged(QBluetoothLocalDevice::HostMode)),
+ this, SLOT(processHostModeChange(QBluetoothLocalDevice::HostMode)));
+ QObject::connect(receiver, SIGNAL(pairingStateChanged(QBluetoothAddress,QBluetoothLocalDevice::Pairing)),
+ this, SLOT(processPairingStateChanged(QBluetoothAddress,QBluetoothLocalDevice::Pairing)));
+ QObject::connect(receiver, SIGNAL(connectDeviceChanges(QBluetoothAddress,bool)),
+ this, SLOT(processConnectDeviceChanges(QBluetoothAddress,bool)));
+ QObject::connect(receiver, SIGNAL(pairingDisplayConfirmation(QBluetoothAddress,QString)),
+ this, SLOT(processDisplayConfirmation(QBluetoothAddress,QString)));
+}
+
+
+QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate()
+{
+ delete receiver;
+ delete obj;
+}
+
+QAndroidJniObject *QBluetoothLocalDevicePrivate::adapter()
+{
+ return obj;
+}
+
+void QBluetoothLocalDevicePrivate::initialize(const QBluetoothAddress &address)
+{
+ QAndroidJniEnvironment env;
+
+ jclass btAdapterClass = env->FindClass("android/bluetooth/BluetoothAdapter");
+ if (btAdapterClass == NULL) {
+ qCWarning(QT_BT_ANDROID) << "Native registration unable to find class android/bluetooth/BluetoothAdapter";
+ return;
+ }
+
+ jmethodID getDefaultAdapterID = env->GetStaticMethodID(btAdapterClass, "getDefaultAdapter", "()Landroid/bluetooth/BluetoothAdapter;");
+ if (getDefaultAdapterID == NULL) {
+ qCWarning(QT_BT_ANDROID) << "Native registration unable to get method ID: getDefaultAdapter of android/bluetooth/BluetoothAdapter";
+ return;
+ }
+
+
+ jobject btAdapterObject = env->CallStaticObjectMethod(btAdapterClass, getDefaultAdapterID);
+ if (btAdapterObject == NULL) {
+ qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth";
+ env->DeleteLocalRef(btAdapterClass);
+ return;
+ }
+
+ obj = new QAndroidJniObject(btAdapterObject);
+ if (!obj->isValid()) {
+ delete obj;
+ obj = 0;
+ } else {
+ if (!address.isNull()) {
+ const QString localAddress = obj->callObjectMethod("getAddress", "()Ljava/lang/String;").toString();
+ if (localAddress != address.toString()) {
+ //passed address not local one -> invalid
+ delete obj;
+ obj = 0;
+ }
+ }
+ }
+
+ env->DeleteLocalRef(btAdapterObject);
+ env->DeleteLocalRef(btAdapterClass);
+}
+
+bool QBluetoothLocalDevicePrivate::isValid() const
+{
+ return obj ? true : false;
+}
+
+
+void QBluetoothLocalDevicePrivate::processHostModeChange(QBluetoothLocalDevice::HostMode newMode)
+{
+ if (!pendingHostModeTransition) {
+ //if not in transition -> pass data on
+ emit q_ptr->hostModeStateChanged(newMode);
+ return;
+ }
+
+ if (isValid() && newMode == QBluetoothLocalDevice::HostPoweredOff) {
+ bool success = (bool) obj->callMethod<jboolean>("enable", "()Z");
+ if (!success)
+ emit q_ptr->error(QBluetoothLocalDevice::UnknownError);
+ }
+
+ pendingHostModeTransition = false;
+}
+
+// Return -1 if address is not part of a pending pairing request
+// Otherwise it returns the index of address in pendingPairings
+int QBluetoothLocalDevicePrivate::pendingPairing(const QBluetoothAddress &address)
+{
+ for (int i = 0; i < pendingPairings.count(); i++) {
+ if (pendingPairings.at(i).first == address)
+ return i;
+ }
+
+ return -1;
+}
+
+
+void QBluetoothLocalDevicePrivate::processPairingStateChanged(
+ const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing)
+{
+ int index = pendingPairing(address);
+
+ if (index < 0)
+ return; //ignore unrelated pairing signals
+
+ QPair<QBluetoothAddress, bool> entry = pendingPairings.takeAt(index);
+ if ((entry.second && pairing == QBluetoothLocalDevice::Paired) ||
+ (!entry.second && pairing == QBluetoothLocalDevice::Unpaired)) {
+ emit q_ptr->pairingFinished(address, pairing);
+ } else {
+ emit q_ptr->error(QBluetoothLocalDevice::PairingError);
+ }
+
+}
+
+void QBluetoothLocalDevicePrivate::processConnectDeviceChanges(const QBluetoothAddress& address, bool isConnectEvent)
+{
+ int index = -1;
+ for (int i = 0; i < connectedDevices.count(); i++) {
+ if (connectedDevices.at(i) == address) {
+ index = i;
+ break;
+ }
+ }
+
+ if (isConnectEvent) { //connect event
+ if (index >= 0)
+ return;
+ connectedDevices.append(address);
+ emit q_ptr->deviceConnected(address);
+ } else { //disconnect event
+ connectedDevices.removeAll(address);
+ emit q_ptr->deviceDisconnected(address);
+ }
+}
+
+void QBluetoothLocalDevicePrivate::processDisplayConfirmation(const QBluetoothAddress &address, const QString &pin)
+{
+ //only send pairing notification for pairing requests issued by
+ //this QBluetoothLocalDevice instance
+ if (pendingPairing(address) == -1)
+ return;
+
+ emit q_ptr->pairingDisplayConfirmation(address, pin);
+ emit q_ptr->pairingDisplayPinCode(address, pin);
+}
+
+QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent)
+: QObject(parent),
+ d_ptr(new QBluetoothLocalDevicePrivate(this, QBluetoothAddress()))
+{
+}
+
+QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent)
+: QObject(parent),
+ d_ptr(new QBluetoothLocalDevicePrivate(this, address))
+{
+}
+
+QString QBluetoothLocalDevice::name() const
+{
+ if (d_ptr->adapter())
+ return d_ptr->adapter()->callObjectMethod("getName", "()Ljava/lang/String;").toString();
+
+ return QString();
+}
+
+QBluetoothAddress QBluetoothLocalDevice::address() const
+{
+ QString result;
+ if (d_ptr->adapter())
+ result = d_ptr->adapter()->callObjectMethod("getAddress", "()Ljava/lang/String;").toString();
+
+ QBluetoothAddress address(result);
+ return address;
+}
+
+void QBluetoothLocalDevice::powerOn()
+{
+ if (hostMode() != HostPoweredOff)
+ return;
+
+ if (d_ptr->adapter()) {
+ bool ret = (bool) d_ptr->adapter()->callMethod<jboolean>("enable", "()Z");
+ if (!ret)
+ emit error(QBluetoothLocalDevice::UnknownError);
+ }
+}
+
+void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode requestedMode)
+{
+ QBluetoothLocalDevice::HostMode mode = requestedMode;
+ if (requestedMode == HostDiscoverableLimitedInquiry)
+ mode = HostDiscoverable;
+
+ if (mode == hostMode())
+ return;
+
+ if (mode == QBluetoothLocalDevice::HostPoweredOff) {
+ bool success = false;
+ if (d_ptr->adapter())
+ success = (bool) d_ptr->adapter()->callMethod<jboolean>("disable", "()Z");
+
+ if (!success)
+ emit error(QBluetoothLocalDevice::UnknownError);
+ } else if (mode == QBluetoothLocalDevice::HostConnectable) {
+ if (hostMode() == QBluetoothLocalDevice::HostDiscoverable) {
+ //cannot directly go from Discoverable to Connectable
+ //we need to go to disabled mode and enable once disabling came through
+
+ setHostMode(QBluetoothLocalDevice::HostPoweredOff);
+ d_ptr->pendingHostModeTransition = true;
+ } else {
+ QAndroidJniObject::callStaticMethod<void>("org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver", "setConnectable");
+ }
+ } else if (mode == QBluetoothLocalDevice::HostDiscoverable ||
+ mode == QBluetoothLocalDevice::HostDiscoverableLimitedInquiry) {
+ QAndroidJniObject::callStaticMethod<void>("org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver", "setDiscoverable");
+ }
+}
+
+QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const
+{
+ if (d_ptr->adapter()) {
+ jint scanMode = d_ptr->adapter()->callMethod<jint>("getScanMode");
+
+ switch (scanMode) {
+ case 20: //BluetoothAdapter.SCAN_MODE_NONE
+ return HostPoweredOff;
+ case 21: //BluetoothAdapter.SCAN_MODE_CONNECTABLE
+ return HostConnectable;
+ case 23: //BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
+ return HostDiscoverable;
+ default:
+ break;
+ }
+ }
+
+ return HostPoweredOff;
+}
+
+QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
+{
+ //Android only supports max of one device (so far)
+ QList<QBluetoothHostInfo> localDevices;
+
+ QAndroidJniEnvironment env;
+ jclass btAdapterClass = env->FindClass("android/bluetooth/BluetoothAdapter");
+ if (btAdapterClass == NULL) {
+ qCWarning(QT_BT_ANDROID) << "Native registration unable to find class android/bluetooth/BluetoothAdapter";
+ return localDevices;
+ }
+
+ jmethodID getDefaultAdapterID = env->GetStaticMethodID(btAdapterClass, "getDefaultAdapter", "()Landroid/bluetooth/BluetoothAdapter;");
+ if (getDefaultAdapterID == NULL) {
+ qCWarning(QT_BT_ANDROID) << "Native registration unable to get method ID: getDefaultAdapter of android/bluetooth/BluetoothAdapter";
+ env->DeleteLocalRef(btAdapterClass);
+ return localDevices;
+ }
+
+
+ jobject btAdapterObject = env->CallStaticObjectMethod(btAdapterClass, getDefaultAdapterID);
+ if (btAdapterObject == NULL) {
+ qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth";
+ env->DeleteLocalRef(btAdapterClass);
+ return localDevices;
+ }
+
+ QAndroidJniObject o(btAdapterObject);
+ if (o.isValid()) {
+ QBluetoothHostInfo info;
+ info.setName(o.callObjectMethod("getName", "()Ljava/lang/String;").toString());
+ info.setAddress(QBluetoothAddress(o.callObjectMethod("getAddress", "()Ljava/lang/String;").toString()));
+ localDevices.append(info);
+ }
+
+ env->DeleteLocalRef(btAdapterObject);
+ env->DeleteLocalRef(btAdapterClass);
+
+ return localDevices;
+}
+
+void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing)
+{
+ if (address.isNull()) {
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QBluetoothLocalDevice::Error,
+ QBluetoothLocalDevice::PairingError));
+ return;
+ }
+
+ const Pairing previousPairing = pairingStatus(address);
+ Pairing newPairing = pairing;
+ if (pairing == AuthorizedPaired) //AuthorizedPaired same as Paired on Android
+ newPairing = Paired;
+
+ if (previousPairing == newPairing) {
+ QMetaObject::invokeMethod(this, "pairingFinished", Qt::QueuedConnection,
+ Q_ARG(QBluetoothAddress, address),
+ Q_ARG(QBluetoothLocalDevice::Pairing, pairing));
+ return;
+ }
+
+ //BluetoothDevice::createBond() requires Android API 19
+ if (QtAndroidPrivate::androidSdkVersion() < 19 || !d_ptr->adapter()) {
+ qCWarning(QT_BT_ANDROID) << "Unable to pair: requires Android API 19+";
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QBluetoothLocalDevice::Error,
+ QBluetoothLocalDevice::PairingError));
+ return;
+ }
+
+ QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString());
+ jboolean success = QAndroidJniObject::callStaticMethod<jboolean>(
+ "org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver",
+ "setPairingMode",
+ "(Ljava/lang/String;Z)Z",
+ inputString.object<jstring>(),
+ newPairing == Paired ? JNI_TRUE : JNI_FALSE);
+
+ if (!success) {
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QBluetoothLocalDevice::Error,
+ QBluetoothLocalDevice::PairingError));
+ } else {
+ d_ptr->pendingPairings.append(qMakePair(address,
+ newPairing == Paired ? true : false));
+ }
+
+}
+
+QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(const QBluetoothAddress &address) const
+{
+ if (address.isNull() || !d_ptr->adapter())
+ return Unpaired;
+
+ QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString());
+ QAndroidJniObject remoteDevice =
+ d_ptr->adapter()->callObjectMethod("getRemoteDevice",
+ "(Ljava/lang/String;)Landroid/bluetooth/BluetoothDevice;",
+ inputString.object<jstring>());
+ QAndroidJniEnvironment env;
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ return Unpaired;
+ }
+
+ jint bondState = remoteDevice.callMethod<jint>("getBondState");
+ switch (bondState) {
+ case 12: //BluetoothDevice.BOND_BONDED
+ return Paired;
+ default:
+ break;
+ }
+
+ return Unpaired;
+}
+
+void QBluetoothLocalDevice::pairingConfirmation(bool confirmation)
+{
+ if (!d_ptr->adapter())
+ return;
+
+ bool success = d_ptr->receiver->pairingConfirmation(confirmation);
+ if (!success)
+ emit error(PairingError);
+
+}
+
+QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
+{
+ //TODO Support BLuetoothManager::getConnectedDevices(int) from API 18 onwards
+ return d_ptr->connectedDevices;
+}
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothlocaldevice_p.h b/src/bluetooth/qbluetoothlocaldevice_p.h
index 76a7e460..61527d31 100644
--- a/src/bluetooth/qbluetoothlocaldevice_p.h
+++ b/src/bluetooth/qbluetoothlocaldevice_p.h
@@ -65,12 +65,58 @@ QT_END_NAMESPACE
#include <QSocketNotifier>
#include "qnx/ppshelpers_p.h"
#endif
+#ifdef QT_ANDROID_BLUETOOTH
+#include <jni.h>
+#include <QtAndroidExtras/QAndroidJniEnvironment>
+#include <QtAndroidExtras/QAndroidJniObject>
+#include <QtCore/QPair>
+#endif
QT_BEGIN_NAMESPACE
class QBluetoothAddress;
-#if defined(QT_BLUEZ_BLUETOOTH)
+#ifdef QT_ANDROID_BLUETOOTH
+class LocalDeviceBroadcastReceiver;
+class QBluetoothLocalDevicePrivate : public QObject
+{
+ Q_OBJECT
+public:
+ QBluetoothLocalDevicePrivate(
+ QBluetoothLocalDevice *q,
+ const QBluetoothAddress &address = QBluetoothAddress());
+ ~QBluetoothLocalDevicePrivate();
+
+ QAndroidJniObject *adapter();
+ void initialize(const QBluetoothAddress& address);
+ static bool startDiscovery();
+ static bool cancelDiscovery();
+ static bool isDiscovering();
+ bool isValid() const;
+
+
+private slots:
+ void processHostModeChange(QBluetoothLocalDevice::HostMode newMode);
+ void processPairingStateChanged(const QBluetoothAddress &address,
+ QBluetoothLocalDevice::Pairing pairing);
+ void processConnectDeviceChanges(const QBluetoothAddress &address, bool isConnectEvent);
+ void processDisplayConfirmation(const QBluetoothAddress &address, const QString &pin);
+
+private:
+ QBluetoothLocalDevice *q_ptr;
+ QAndroidJniObject *obj;
+
+ int pendingPairing(const QBluetoothAddress &address);
+
+public:
+ LocalDeviceBroadcastReceiver *receiver;
+ bool pendingHostModeTransition;
+ QList<QPair<QBluetoothAddress, bool> > pendingPairings;
+
+ QList<QBluetoothAddress> connectedDevices;
+};
+
+#elif defined(QT_BLUEZ_BLUETOOTH)
class QBluetoothLocalDevicePrivate : public QObject,
protected QDBusContext
{
diff --git a/src/bluetooth/qbluetoothserver.cpp b/src/bluetooth/qbluetoothserver.cpp
index e70ec1db..670b522f 100644
--- a/src/bluetooth/qbluetoothserver.cpp
+++ b/src/bluetooth/qbluetoothserver.cpp
@@ -90,7 +90,8 @@ QT_BEGIN_NAMESPACE
/*!
\fn void QBluetoothServer::close()
- Closes and resets the listening socket.
+ Closes and resets the listening socket. Any already established \l QBluetoothSocket
+ continues to operate and must be separately \l {QBluetoothSocket::close()}{closed}.
*/
/*!
@@ -110,7 +111,11 @@ QT_BEGIN_NAMESPACE
/*!
\fn bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
- Start listening for incoming connections to \a address on \a port.
+ Start listening for incoming connections to \a address on \a port. \a address
+ must be a local Bluetooth adapter address and \a port must be larger than zero
+ and not be taken already by another Bluetooth server object. It is recommended
+ to avoid setting a port number to enable the system to automatically choose
+ a port.
Returns \c true if the operation succeeded and the server is listening for
incoming connections, otherwise returns \c false.
@@ -124,7 +129,8 @@ QT_BEGIN_NAMESPACE
/*!
\fn void QBluetoothServer::setMaxPendingConnections(int numConnections)
- Sets the maximum number of pending connections to \a numConnections.
+ Sets the maximum number of pending connections to \a numConnections. If
+ the number of pending sockets exceeds this limit new sockets will be rejected.
\sa maxPendingConnections()
*/
@@ -207,10 +213,13 @@ QBluetoothServiceInfo QBluetoothServer::listen(const QBluetoothUuid &uuid, const
QBluetoothServiceInfo::Sequence classId;
classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
- serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
classId);
+ //Android requires custom uuid to be set as service class and not as service uuid
+ classId.prepend(QVariant::fromValue(uuid));
+ serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
+
serviceInfo.setServiceUuid(uuid);
QBluetoothServiceInfo::Sequence protocolDescriptorList;
@@ -248,6 +257,8 @@ bool QBluetoothServer::isListening() const
#ifdef QT_QNX_BLUETOOTH
if (!d->socket)
return false;
+#elif defined(QT_ANDROID_BLUETOOTH)
+ return d->isListening();
#endif
return d->socket->state() == QBluetoothSocket::ListeningState;
@@ -268,6 +279,13 @@ int QBluetoothServer::maxPendingConnections() const
/*!
\fn QBluetoothServer::setSecurityFlags(QBluetooth::SecurityFlags security)
Sets the Bluetooth security flags to \a security. This function must be called before calling listen().
+ The Bluetooth link will always be encrypted when using Bluetooth 2.1 devices as encryption is
+ mandatory.
+
+ Android only supports two levels of security (secure and non-secure). If this flag is set to
+ \l QBluetooth::NoSecurity the server object will not employ any authentication or encryption.
+ Any other security flag combination will trigger a secure Bluetooth connection.
+
On BlackBerry, security flags are not supported and will be ignored.
*/
diff --git a/src/bluetooth/qbluetoothserver.h b/src/bluetooth/qbluetoothserver.h
index d4468cb6..9b1d54c4 100644
--- a/src/bluetooth/qbluetoothserver.h
+++ b/src/bluetooth/qbluetoothserver.h
@@ -97,7 +97,7 @@ public:
Q_SIGNALS:
void newConnection();
- void error(Error);
+ void error(QBluetoothServer::Error);
protected:
QBluetoothServerPrivate *d_ptr;
diff --git a/src/bluetooth/qbluetoothserver_android.cpp b/src/bluetooth/qbluetoothserver_android.cpp
new file mode 100644
index 00000000..a2d08757
--- /dev/null
+++ b/src/bluetooth/qbluetoothserver_android.cpp
@@ -0,0 +1,274 @@
+/***************************************************************************
+**
+** 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 <QtCore/QLoggingCategory>
+#include "qbluetoothserver.h"
+#include "qbluetoothserver_p.h"
+#include "qbluetoothsocket.h"
+#include "qbluetoothsocket_p.h"
+#include "qbluetoothlocaldevice.h"
+#include "android/serveracceptancethread_p.h"
+
+#include <QCoreApplication>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+QHash<QBluetoothServerPrivate*, int> __fakeServerPorts;
+
+QBluetoothServerPrivate::QBluetoothServerPrivate(QBluetoothServiceInfo::Protocol sType)
+ : socket(0),maxPendingConnections(1), securityFlags(QBluetooth::NoSecurity), serverType(sType),
+ m_lastError(QBluetoothServer::NoError)
+{
+ thread = new ServerAcceptanceThread();
+ thread->setMaxPendingConnections(maxPendingConnections);
+}
+
+QBluetoothServerPrivate::~QBluetoothServerPrivate()
+{
+ Q_Q(QBluetoothServer);
+ if (isListening())
+ q->close();
+
+ __fakeServerPorts.remove(this);
+
+ if (thread->isRunning()) {
+ thread->stop();
+ thread->wait();
+ }
+ thread->deleteLater();
+ thread = 0;
+}
+
+bool QBluetoothServerPrivate::initiateActiveListening(
+ const QBluetoothUuid& uuid, const QString &serviceName)
+{
+ Q_UNUSED(uuid);
+ Q_UNUSED(serviceName);
+ qCDebug(QT_BT_ANDROID) << "Initiate active listening" << uuid.toString() << serviceName;
+
+ if (uuid.isNull() || serviceName.isEmpty())
+ return false;
+
+ //no change of SP profile details -> do nothing
+ if (uuid == m_uuid && serviceName == m_serviceName)
+ return true;
+
+ m_uuid = uuid;
+ m_serviceName = serviceName;
+ thread->setServiceDetails(m_uuid, m_serviceName, securityFlags);
+
+ Q_ASSERT(!thread->isRunning());
+ thread->start();
+ Q_ASSERT(thread->isRunning());
+
+ return true;
+}
+
+bool QBluetoothServerPrivate::deactivateActiveListening()
+{
+ thread->stop();
+ thread->wait();
+
+ return true;
+}
+
+bool QBluetoothServerPrivate::isListening() const
+{
+ return thread->isRunning();
+}
+
+void QBluetoothServer::close()
+{
+ Q_D(QBluetoothServer);
+
+ d->thread->stop();
+ d->thread->wait();
+
+ if (d->thread)
+ d->thread->disconnect();
+ __fakeServerPorts.remove(d);
+}
+
+bool QBluetoothServer::listen(const QBluetoothAddress &localAdapter, quint16 port)
+{
+ const QList<QBluetoothHostInfo> localDevices = QBluetoothLocalDevice::allDevices();
+ if (!localDevices.count())
+ return false; //no Bluetooth device
+
+ if (!localAdapter.isNull()) {
+ bool found = false;
+ foreach (const QBluetoothHostInfo &hostInfo, localDevices) {
+ if (hostInfo.address() == localAdapter) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ qCWarning(QT_BT_ANDROID) << localAdapter.toString() << "is not a valid local Bt adapter";
+ return false;
+ }
+ }
+
+ Q_D(QBluetoothServer);
+ if (serverType() != QBluetoothServiceInfo::RfcommProtocol) {
+ d->m_lastError = UnsupportedProtocolError;
+ emit error(d->m_lastError);
+ return false;
+ }
+
+ if (d->isListening())
+ return false;
+
+ //check Bluetooth is available and online
+ QAndroidJniObject btAdapter = QAndroidJniObject::callStaticObjectMethod(
+ "android/bluetooth/BluetoothAdapter",
+ "getDefaultAdapter",
+ "()Landroid/bluetooth/BluetoothAdapter;");
+ if (!btAdapter.isValid()) {
+ qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth";
+ d->m_lastError = QBluetoothServer::UnknownError;
+ emit error(d->m_lastError);
+ return false;
+ }
+
+ const int state = btAdapter.callMethod<jint>("getState");
+ if (state != 12 ) { //BluetoothAdapter.STATE_ON
+ d->m_lastError = QBluetoothServer::PoweredOffError;
+ emit error(d->m_lastError);
+ qCWarning(QT_BT_ANDROID) << "Bluetooth device is powered off";
+ return false;
+ }
+
+ //We can not register an actual Rfcomm port, because the platform does not allow it
+ //but we need a way to associate a server with a service
+ if (port == 0) { //Try to assign a non taken port id
+ for (int i=1; ; i++){
+ if (__fakeServerPorts.key(i) == 0) {
+ port = i;
+ break;
+ }
+ }
+ }
+
+ if (__fakeServerPorts.key(port) == 0) {
+ __fakeServerPorts[d] = port;
+
+ qCDebug(QT_BT_ANDROID) << "Port" << port << "registered";
+ } else {
+ qCWarning(QT_BT_ANDROID) << "server with port" << port << "already registered or port invalid";
+ d->m_lastError = ServiceAlreadyRegisteredError;
+ emit error(d->m_lastError);
+ return false;
+ }
+
+ connect(d->thread, SIGNAL(newConnection()), this, SIGNAL(newConnection()));
+ return true;
+}
+
+void QBluetoothServer::setMaxPendingConnections(int numConnections)
+{
+ Q_D(QBluetoothServer);
+ d->maxPendingConnections = numConnections;
+ d->thread->setMaxPendingConnections(numConnections);
+}
+
+QBluetoothAddress QBluetoothServer::serverAddress() const
+{
+ //Android only supports one local adapter
+ QList<QBluetoothHostInfo> hosts = QBluetoothLocalDevice::allDevices();
+ Q_ASSERT(hosts.count() <= 1);
+
+ if (hosts.isEmpty())
+ return QBluetoothAddress();
+ else
+ return hosts.at(0).address();
+}
+
+quint16 QBluetoothServer::serverPort() const
+{
+ //We return the fake port
+ Q_D(const QBluetoothServer);
+ return __fakeServerPorts.value((QBluetoothServerPrivate*)d, 0);
+}
+
+bool QBluetoothServer::hasPendingConnections() const
+{
+ Q_D(const QBluetoothServer);
+
+ return d->thread->hasPendingConnections();
+}
+
+QBluetoothSocket *QBluetoothServer::nextPendingConnection()
+{
+ Q_D(const QBluetoothServer);
+
+ QAndroidJniObject socket = d->thread->nextPendingConnection();
+ if (!socket.isValid())
+ return 0;
+
+
+ QBluetoothSocket *newSocket = new QBluetoothSocket();
+ bool success = newSocket->d_ptr->setSocketDescriptor(socket, d->serverType);
+ if (!success) {
+ delete newSocket;
+ newSocket = 0;
+ }
+
+ return newSocket;
+}
+
+void QBluetoothServer::setSecurityFlags(QBluetooth::SecurityFlags security)
+{
+ Q_D(QBluetoothServer);
+ d->securityFlags = security;
+}
+
+QBluetooth::SecurityFlags QBluetoothServer::securityFlags() const
+{
+ Q_D(const QBluetoothServer);
+ return d->securityFlags;
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/bluetooth/qbluetoothserver_bluez.cpp b/src/bluetooth/qbluetoothserver_bluez.cpp
index 8acd8e20..3cbb708c 100644
--- a/src/bluetooth/qbluetoothserver_bluez.cpp
+++ b/src/bluetooth/qbluetoothserver_bluez.cpp
@@ -201,7 +201,7 @@ bool QBluetoothServer::hasPendingConnections() const
if (!d || !d->socketNotifier)
return false;
- // if the socket notifier is disable there is a pending connection waiting for us to accept.
+ // if the socket notifier is disabled there is a pending connection waiting for us to accept.
return !d->socketNotifier->isEnabled();
}
diff --git a/src/bluetooth/qbluetoothserver_p.h b/src/bluetooth/qbluetoothserver_p.h
index 29055fbd..4137986a 100644
--- a/src/bluetooth/qbluetoothserver_p.h
+++ b/src/bluetooth/qbluetoothserver_p.h
@@ -56,6 +56,14 @@
QT_FORWARD_DECLARE_CLASS(QSocketNotifier)
#endif
+#ifdef QT_ANDROID_BLUETOOTH
+#include <QtAndroidExtras/QAndroidJniEnvironment>
+#include <QtAndroidExtras/QAndroidJniObject>
+#include <QtBluetooth/QBluetoothUuid>
+
+class ServerAcceptanceThread;
+#endif
+
QT_BEGIN_NAMESPACE
class QBluetoothAddress;
@@ -108,6 +116,15 @@ private Q_SLOTS:
void controlEvent(ppsResult result);
#elif defined(QT_BLUEZ_BLUETOOTH)
QSocketNotifier *socketNotifier;
+#elif defined(QT_ANDROID_BLUETOOTH)
+ ServerAcceptanceThread *thread;
+ QString m_serviceName;
+ QBluetoothUuid m_uuid;
+public:
+ bool isListening() const;
+ bool initiateActiveListening(const QBluetoothUuid& uuid, const QString &serviceName);
+ bool deactivateActiveListening();
+
#endif
};
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent.cpp b/src/bluetooth/qbluetoothservicediscoveryagent.cpp
index 4df6a4ea..7e0e701d 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent.cpp
@@ -435,8 +435,6 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryFinished()
return;
}
-// discoveredDevices = deviceDiscoveryAgent->discoveredDevices();
-
delete deviceDiscoveryAgent;
deviceDiscoveryAgent = 0;
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent.h b/src/bluetooth/qbluetoothservicediscoveryagent.h
index cad89d0d..59c7b74b 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent.h
+++ b/src/bluetooth/qbluetoothservicediscoveryagent.h
@@ -115,6 +115,12 @@ private:
Q_PRIVATE_SLOT(d_func(), void _q_discoveredServices(QDBusPendingCallWatcher*))
Q_PRIVATE_SLOT(d_func(), void _q_createdDevice(QDBusPendingCallWatcher*))
#endif
+#ifdef QT_ANDROID_BLUETOOTH
+ Q_PRIVATE_SLOT(d_func(), void _q_processFetchedUuids(const QBluetoothAddress &address,
+ const QList<QBluetoothUuid>&))
+ Q_PRIVATE_SLOT(d_func(), void _q_fetchUuidsTimeout())
+ Q_PRIVATE_SLOT(d_func(), void _q_hostModeStateChanged(QBluetoothLocalDevice::HostMode state))
+#endif
};
QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
new file mode 100644
index 00000000..72a4d03b
--- /dev/null
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_android.cpp
@@ -0,0 +1,513 @@
+/****************************************************************************
+**
+** 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 <QtCore/QLoggingCategory>
+#include <QtCore/QTimer>
+#include <QtCore/private/qjnihelpers_p.h>
+#include <QtAndroidExtras/QAndroidJniEnvironment>
+#include <QtBluetooth/QBluetoothHostInfo>
+#include <QtBluetooth/QBluetoothLocalDevice>
+#include <QtBluetooth/QBluetoothServiceDiscoveryAgent>
+
+#include "qbluetoothservicediscoveryagent_p.h"
+#include "android/servicediscoverybroadcastreceiver_p.h"
+#include "android/localdevicebroadcastreceiver_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+Q_GLOBAL_STATIC_WITH_ARGS(QUuid, btBaseUuid, ("{00000000-0000-1000-8000-00805F9B34FB}"))
+
+QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
+ const QBluetoothAddress &deviceAdapter)
+ : error(QBluetoothServiceDiscoveryAgent::NoError),
+ state(Inactive), deviceDiscoveryAgent(0),
+ mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery),
+ singleDevice(false), receiver(0), localDeviceReceiver(0)
+{
+ QList<QBluetoothHostInfo> devices = QBluetoothLocalDevice::allDevices();
+ Q_ASSERT(devices.count() == 1); //Android only supports one device at the moment
+
+ if (deviceAdapter.isNull() && devices.count() > 0 )
+ m_deviceAdapterAddress = devices.at(0).address();
+ else
+ m_deviceAdapterAddress = deviceAdapter;
+
+ if (QtAndroidPrivate::androidSdkVersion() < 15)
+ qCWarning(QT_BT_ANDROID)
+ << "SDP not supported by Android API below version 15. Detected version: "
+ << QtAndroidPrivate::androidSdkVersion()
+ << "Service discovery will return empty list.";
+
+
+ /* We assume that the current local adapter has been passed.
+ Android only supports one adapter at the moment. If m_deviceAdapterAddress
+ doesn't match the local adapter then we won't get to this point since
+ we have an InvalidBluetoothAdapter error.
+
+ The logic below must change once there is more than one adapter.
+ */
+
+ btAdapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter",
+ "getDefaultAdapter",
+ "()Landroid/bluetooth/BluetoothAdapter;");
+ if (!btAdapter.isValid())
+ qCWarning(QT_BT_ANDROID) << "Platform does not support Bluetooth";
+
+ qRegisterMetaType<QList<QBluetoothUuid> >("QList<QBluetoothUuid>");
+}
+
+QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
+{
+ delete receiver;
+ delete localDeviceReceiver;
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
+{
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+
+ if (!btAdapter.isValid()) {
+ error = QBluetoothServiceDiscoveryAgent::UnknownError;
+ errorString = QBluetoothServiceDiscoveryAgent::tr("Platform does not support Bluetooth.");
+
+ //abort any outstanding discoveries
+ discoveredDevices.clear();
+ emit q->error(error);
+ _q_serviceDiscoveryFinished();
+
+ return;
+ }
+
+ /* SDP discovery was officially added by Android API v15
+ * BluetoothDevice.getUuids() existed in earlier APIs already and in the future we may use
+ * reflection to support earlier Android versions than 15. Unfortunately
+ * BluetoothDevice.fetchUuidsWithSdp() and related APIs had some structure changes
+ * over time. Therefore we won't attempt this with reflection.
+ *
+ * TODO: Use reflection to support getUuuids() where possible.
+ * */
+ if (QtAndroidPrivate::androidSdkVersion() < 15) {
+ qCWarning(QT_BT_ANDROID) << "Aborting SDP enquiry due to too low Android API version (requires v15+)";
+
+ error = QBluetoothServiceDiscoveryAgent::UnknownError;
+ errorString = QBluetoothServiceDiscoveryAgent::tr("Android API below v15 does not support SDP discovery.");
+
+ //abort any outstanding discoveries
+ sdpCache.clear();
+ discoveredDevices.clear();
+ emit q->error(error);
+ _q_serviceDiscoveryFinished();
+
+ return;
+ }
+
+ QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString());
+ QAndroidJniObject remoteDevice =
+ btAdapter.callObjectMethod("getRemoteDevice",
+ "(Ljava/lang/String;)Landroid/bluetooth/BluetoothDevice;",
+ inputString.object<jstring>());
+ QAndroidJniEnvironment env;
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ env->ExceptionDescribe();
+
+ //if it was only device then its error -> otherwise go to next device
+ if (singleDevice) {
+ error = QBluetoothServiceDiscoveryAgent::InputOutputError;
+ errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot create Android BluetoothDevice.");
+
+ qCWarning(QT_BT_ANDROID) << "Cannot start SDP for" << discoveredDevices.at(0).name()
+ << "(" << address.toString() << ")";
+ emit q->error(error);
+ }
+ _q_serviceDiscoveryFinished();
+ return;
+ }
+
+
+ if (mode == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) {
+ qCDebug(QT_BT_ANDROID) << "Minimal discovery on (" << discoveredDevices.at(0).name()
+ << ")" << address.toString() ;
+
+ //Minimal discovery uses BluetoothDevice.getUuids()
+ QAndroidJniObject parcelUuidArray = remoteDevice.callObjectMethod(
+ "getUuids", "()[Landroid/os/ParcelUuid;");
+
+ if (!parcelUuidArray.isValid()) {
+ if (singleDevice) {
+ error = QBluetoothServiceDiscoveryAgent::InputOutputError;
+ errorString = QBluetoothServiceDiscoveryAgent::tr("Cannot obtain service uuids.");
+ emit q->error(error);
+ }
+ qCWarning(QT_BT_ANDROID) << "Cannot retrieve SDP UUIDs for" << discoveredDevices.at(0).name()
+ << "(" << address.toString() << ")";
+ _q_serviceDiscoveryFinished();
+ return;
+ }
+
+ const QList<QBluetoothUuid> results = ServiceDiscoveryBroadcastReceiver::convertParcelableArray(parcelUuidArray);
+ populateDiscoveredServices(discoveredDevices.at(0), results);
+
+ _q_serviceDiscoveryFinished();
+ } else {
+ qCDebug(QT_BT_ANDROID) << "Full discovery on (" << discoveredDevices.at(0).name()
+ << ")" << address.toString();
+
+ //Full discovery uses BluetoothDevice.fetchUuidsWithSdp()
+ if (!receiver) {
+ receiver = new ServiceDiscoveryBroadcastReceiver();
+ QObject::connect(receiver, SIGNAL(uuidFetchFinished(QBluetoothAddress,QList<QBluetoothUuid>)),
+ q, SLOT(_q_processFetchedUuids(const QBluetoothAddress&,const QList<QBluetoothUuid>&)));
+ }
+
+ if (!localDeviceReceiver) {
+ localDeviceReceiver = new LocalDeviceBroadcastReceiver();
+ QObject::connect(localDeviceReceiver, SIGNAL(hostModeStateChanged(QBluetoothLocalDevice::HostMode)),
+ q, SLOT(_q_hostModeStateChanged(QBluetoothLocalDevice::HostMode)));
+ }
+
+ jboolean result = remoteDevice.callMethod<jboolean>("fetchUuidsWithSdp");
+ if (!result) {
+ //kill receiver to limit load of signals
+ receiver->deleteLater();
+ receiver = 0;
+ qCWarning(QT_BT_ANDROID) << "Cannot start dynamic fetch.";
+ _q_serviceDiscoveryFinished();
+ }
+ }
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::stop()
+{
+ sdpCache.clear();
+ discoveredDevices.clear();
+
+ //kill receiver to limit load of signals
+ receiver->deleteLater();
+ receiver = 0;
+
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+ emit q->canceled();
+
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::_q_processFetchedUuids(
+ const QBluetoothAddress &address, const QList<QBluetoothUuid> &uuids)
+{
+ //don't leave more data through if we are not interested anymore
+ if (discoveredDevices.count() == 0)
+ return;
+
+ if (QT_BT_ANDROID().isDebugEnabled()) {
+ qCDebug(QT_BT_ANDROID) << "Found UUID for" << address.toString()
+ << "\ncount: " << uuids.count();
+
+ QString result;
+ for (int i = 0; i<uuids.count(); i++)
+ result += uuids.at(i).toString() + QStringLiteral("**");
+ qCDebug(QT_BT_ANDROID) << result;
+ }
+
+ /* In general there are two uuid events per device.
+ * We'll wait for the second event to arrive before we process the UUIDs.
+ * We utilize a timeout to catch cases when the second
+ * event doesn't arrive at all.
+ * Generally we assume that the second uuid event carries the most up-to-date
+ * set of uuids and discard the first events results.
+ */
+
+ if (sdpCache.contains(address)) {
+ //second event
+ QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair = sdpCache.take(address);
+
+ //prefer second uuid set over first
+ populateDiscoveredServices(pair.first, uuids);
+
+ if (discoveredDevices.count() == 1 && sdpCache.isEmpty()) {
+ //last regular uuid data set from OS -> we finish here
+ _q_serviceDiscoveryFinished();
+ }
+ } else {
+ //first event
+ QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair;
+ pair.first = discoveredDevices.at(0);
+ pair.second = uuids;
+
+ if (pair.first.address() != address)
+ return;
+
+ sdpCache.insert(address, pair);
+
+ //the discovery on the last device cannot immediately finish
+ //we have to grant the 2 seconds timeout delay
+ if (discoveredDevices.count() == 1) {
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+ QTimer::singleShot(4000, q, SLOT(_q_fetchUuidsTimeout()));
+ return;
+ }
+
+ _q_serviceDiscoveryFinished();
+ }
+}
+
+
+static QString serviceNameForClassUuid(const uint value)
+{
+ switch (value & 0xffff) {
+ case QBluetoothUuid::ServiceDiscoveryServer: return QBluetoothServiceDiscoveryAgent::tr("Service Discovery");
+ //case QBluetoothUuid::BrowseGroupDescriptor: return QString();
+ //case QBluetoothUuid::PublicBrowseGroup: return QString();
+ case QBluetoothUuid::SerialPort: return QBluetoothServiceDiscoveryAgent::tr("Serial Port Profile");
+ case QBluetoothUuid::LANAccessUsingPPP: return QBluetoothServiceDiscoveryAgent::tr("LAN Access Profile");
+ case QBluetoothUuid::DialupNetworking: return QBluetoothServiceDiscoveryAgent::tr("Dial-up Networking");
+ case QBluetoothUuid::IrMCSync: return QBluetoothServiceDiscoveryAgent::tr("Synchronization");
+ case QBluetoothUuid::ObexObjectPush: return QBluetoothServiceDiscoveryAgent::tr("Object Push");
+ case QBluetoothUuid::OBEXFileTransfer: return QBluetoothServiceDiscoveryAgent::tr("File Transfer");
+ case QBluetoothUuid::IrMCSyncCommand: return QBluetoothServiceDiscoveryAgent::tr("Synchronization Command");
+ case QBluetoothUuid::Headset: return QBluetoothServiceDiscoveryAgent::tr("Headset");
+ case QBluetoothUuid::AudioSource: return QBluetoothServiceDiscoveryAgent::tr("Advanced Audio Distribution Source");
+ case QBluetoothUuid::AudioSink: return QBluetoothServiceDiscoveryAgent::tr("Advanced Audio Distribution Sink");
+ case QBluetoothUuid::AV_RemoteControlTarget: return QBluetoothServiceDiscoveryAgent::tr("Audio/Video Remote Control Target");
+ case QBluetoothUuid::AdvancedAudioDistribution: return QBluetoothServiceDiscoveryAgent::tr("Advanced Audio Distribution");
+ case QBluetoothUuid::AV_RemoteControl: return QBluetoothServiceDiscoveryAgent::tr("Audio/Video Remote Control");
+ case QBluetoothUuid::AV_RemoteControlController: return QBluetoothServiceDiscoveryAgent::tr("Audio/Video Remote Control Controller");
+ case QBluetoothUuid::HeadsetAG: return QBluetoothServiceDiscoveryAgent::tr("Headset AG");
+ case QBluetoothUuid::PANU: return QBluetoothServiceDiscoveryAgent::tr("Personal Area Networking (PANU)");
+ case QBluetoothUuid::NAP: return QBluetoothServiceDiscoveryAgent::tr("Personal Area Networking (NAP)");
+ case QBluetoothUuid::GN: return QBluetoothServiceDiscoveryAgent::tr("Personak Area Networking (GN)");
+ case QBluetoothUuid::DirectPrinting: return QBluetoothServiceDiscoveryAgent::tr("Basic Printing (DP)");
+ //case QBluetoothUuid::ReferencePrinting: return QBluetoothServiceDiscoveryAgent::tr("");
+ case QBluetoothUuid::ImagingResponder: return QBluetoothServiceDiscoveryAgent::tr("Basic Imaging Responder");
+ case QBluetoothUuid::ImagingAutomaticArchive: return QBluetoothServiceDiscoveryAgent::tr("Basic Imaging Archive");
+ case QBluetoothUuid::ImagingReferenceObjects: return QBluetoothServiceDiscoveryAgent::tr("Basic Imaging Ref Objects");
+ case QBluetoothUuid::Handsfree: return QBluetoothServiceDiscoveryAgent::tr("Hands-Free");
+ case QBluetoothUuid::HandsfreeAudioGateway: return QBluetoothServiceDiscoveryAgent::tr("Hands-Free AG");
+ case QBluetoothUuid::DirectPrintingReferenceObjectsService: return QBluetoothServiceDiscoveryAgent::tr("Basic Printing RefObject Service");
+ case QBluetoothUuid::ReflectedUI: return QBluetoothServiceDiscoveryAgent::tr("Basic Printing Reflected UI");
+ case QBluetoothUuid::BasicPrinting: return QBluetoothServiceDiscoveryAgent::tr("Basic Printing");
+ case QBluetoothUuid::PrintingStatus: return QBluetoothServiceDiscoveryAgent::tr("Basic Printing Status");
+ case QBluetoothUuid::HumanInterfaceDeviceService: return QBluetoothServiceDiscoveryAgent::tr("Human Interface Device");
+ case QBluetoothUuid::HardcopyCableReplacement: return QBluetoothServiceDiscoveryAgent::tr("Hardcopy Cable Replacement");
+ case QBluetoothUuid::HCRPrint: return QBluetoothServiceDiscoveryAgent::tr("Hardcopy Cable Replacement Print");
+ case QBluetoothUuid::HCRScan: return QBluetoothServiceDiscoveryAgent::tr("Hardcopy Cable Replacement Scan");
+ case QBluetoothUuid::SIMAccess: return QBluetoothServiceDiscoveryAgent::tr("SIM Access");
+ case QBluetoothUuid::PhonebookAccessPCE: return QBluetoothServiceDiscoveryAgent::tr("Phonebook Access PCE");
+ case QBluetoothUuid::PhonebookAccessPSE: return QBluetoothServiceDiscoveryAgent::tr("Phonebook Access PSE");
+ case QBluetoothUuid::PhonebookAccess: return QBluetoothServiceDiscoveryAgent::tr("Phonebook Access");
+ case QBluetoothUuid::HeadsetHS: return QBluetoothServiceDiscoveryAgent::tr("Headset HS");
+ case QBluetoothUuid::MessageAccessServer: return QBluetoothServiceDiscoveryAgent::tr("Message Access Server");
+ case QBluetoothUuid::MessageNotificationServer: return QBluetoothServiceDiscoveryAgent::tr("Message Notification Server");
+ case QBluetoothUuid::MessageAccessProfile: return QBluetoothServiceDiscoveryAgent::tr("Message Accress");
+ case QBluetoothUuid::PnPInformation: return QBluetoothServiceDiscoveryAgent::tr("Navigation Satellite System");
+ //case QBluetoothUuid::GenericNetworking: return QBluetoothServiceDiscoveryAgent::tr("");
+ //case QBluetoothUuid::GenericFileTransfer: return QBluetoothServiceDiscoveryAgent::tr("");
+ //case QBluetoothUuid::GenericAudio: return QBluetoothServiceDiscoveryAgent::tr("");
+ //case QBluetoothUuid::GenericTelephony: return QBluetoothServiceDiscoveryAgent::tr("");
+ case QBluetoothUuid::VideoSource: return QBluetoothServiceDiscoveryAgent::tr("Video Source");
+ case QBluetoothUuid::VideoSink: return QBluetoothServiceDiscoveryAgent::tr("Video Sink");
+ case QBluetoothUuid::VideoDistribution: return QBluetoothServiceDiscoveryAgent::tr("Video Distribution");
+ case QBluetoothUuid::HDP: return QBluetoothServiceDiscoveryAgent::tr("Health Device");
+ case QBluetoothUuid::HDPSource: return QBluetoothServiceDiscoveryAgent::tr("Health Device Source");
+ case QBluetoothUuid::HDPSink: return QBluetoothServiceDiscoveryAgent::tr("Health Device Sink");
+ default:
+ break;
+ }
+
+ return QString();
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices(const QBluetoothDeviceInfo &remoteDevice, const QList<QBluetoothUuid> &uuids)
+{
+ /* Android doesn't provide decent SDP data. A list of uuids is close to meaning-less
+ *
+ * The following approach is chosen:
+ * - If we see an SPP service class and we see
+ * one or more custom uuids we match them up. Such services will always be SPP services.
+ * - If we see a custom uuid but no SPP uuid then we return
+ * BluetoothServiceInfo instance with just a servuceUuid (no service class set)
+ * - Any other service uuid will stand on its own.
+ * */
+
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+
+ //find SPP and custom uuid
+ QBluetoothUuid uuid;
+ int sppIndex = -1;
+ QVector<int> customUuids;
+
+ for (int i = 0; i < uuids.count(); i++) {
+ uuid = uuids.at(i);
+
+ if (uuid.isNull())
+ continue;
+
+ bool isBaseUuuidSuffix = false;
+ if (btBaseUuid()->data2 == uuid.data2 && btBaseUuid()->data3 == uuid.data3
+ && btBaseUuid()->data4[0] == uuid.data4[0] && btBaseUuid()->data4[1] == uuid.data4[1]
+ && btBaseUuid()->data4[2] == uuid.data4[2] && btBaseUuid()->data4[3] == uuid.data4[3]
+ && btBaseUuid()->data4[4] == uuid.data4[4] && btBaseUuid()->data4[5] == uuid.data4[5]
+ && btBaseUuid()->data4[6] == uuid.data4[6] && btBaseUuid()->data4[7] == uuid.data4[7])
+ {
+ isBaseUuuidSuffix = true;
+ }
+
+ //check for SPP protocol
+ if (isBaseUuuidSuffix && ((uuid.data1 & 0xffff) == QBluetoothUuid::SerialPort))
+ sppIndex = i;
+
+ if (!isBaseUuuidSuffix)
+ customUuids.append(i);
+ }
+
+ for (int i = 0; i < uuids.count(); i++) {
+ if (i == sppIndex) //skip SPP service class id
+ continue;
+
+ QBluetoothServiceInfo serviceInfo;
+ serviceInfo.setDevice(remoteDevice);
+
+ QBluetoothServiceInfo::Sequence protocolDescriptorList;
+ protocolDescriptorList << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap));
+
+ if (customUuids.contains(i) && sppIndex > -1) {
+ //we have a custom uuid of service class type SPP
+
+ //set rfcomm protocol
+ QBluetoothServiceInfo::Sequence protocol;
+ protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm))
+ << QVariant::fromValue(0);
+ protocolDescriptorList.append(QVariant::fromValue(protocol));
+
+ //set SPP service class uuid
+ QBluetoothServiceInfo::Sequence classId;
+ classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort));
+ serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,
+ classId);
+ classId.prepend(QVariant::fromValue(uuids.at(i)));
+ serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId);
+
+ serviceInfo.setServiceName(QBluetoothServiceDiscoveryAgent::tr("Serial Port Profile"));
+ //TODO Remove line below - work around
+ serviceInfo.setServiceUuid(uuids.at(i));
+ } else if (customUuids.contains(i)) {
+ //custom uuid but no serial port
+ serviceInfo.setServiceUuid(uuids.at(i));
+ }
+
+ //Check if the UUID is in the uuidFilter
+ if (!uuidFilter.isEmpty() && !uuidFilter.contains(serviceInfo.serviceUuid()))
+ continue;
+
+ serviceInfo.setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList);
+ serviceInfo.setAttribute(QBluetoothServiceInfo::BrowseGroupList,
+ QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup));
+
+ if (!customUuids.contains(i)) {
+ //if we don't have custom uuid use it as class id as well
+ QList<QBluetoothUuid> serviceClassId;
+ serviceClassId << uuids.at(i);
+ serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, QVariant::fromValue(serviceClassId));
+ serviceInfo.setServiceName(serviceNameForClassUuid(uuids.at(i).data1));
+ }
+
+ //don't include the service if we already discovered it before
+ bool alreadyDiscovered = false;
+ for (int i = 0; i < discoveredServices.count(); i++) {
+ const QBluetoothServiceInfo &info = discoveredServices.at(i);
+ if (info.device() == serviceInfo.device()
+ && info.serviceClassUuids() == serviceInfo.serviceClassUuids()
+ && info.serviceUuid() == serviceInfo.serviceUuid()) {
+ alreadyDiscovered = true;
+ break;
+ }
+ }
+
+ if (!alreadyDiscovered) {
+ discoveredServices << serviceInfo;
+ //qCDebug(QT_BT_ANDROID) << serviceInfo;
+ emit q->serviceDiscovered(serviceInfo);
+ }
+ }
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::_q_fetchUuidsTimeout()
+{
+ if (sdpCache.isEmpty())
+ return;
+
+ QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > pair;
+ const QList<QBluetoothAddress> keys = sdpCache.keys();
+ foreach (const QBluetoothAddress &key, keys) {
+ pair = sdpCache.take(key);
+ populateDiscoveredServices(pair.first, pair.second);
+ }
+
+ Q_ASSERT(sdpCache.isEmpty());
+
+ //kill receiver to limit load of signals
+ receiver->deleteLater();
+ receiver = 0;
+ _q_serviceDiscoveryFinished();
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::_q_hostModeStateChanged(QBluetoothLocalDevice::HostMode state)
+{
+ if (discoveryState() == QBluetoothServiceDiscoveryAgentPrivate::ServiceDiscovery &&
+ state == QBluetoothLocalDevice::HostPoweredOff ) {
+
+ discoveredDevices.clear();
+ sdpCache.clear();
+ error = QBluetoothServiceDiscoveryAgent::PoweredOffError;
+ errorString = QBluetoothServiceDiscoveryAgent::tr("Device is powered off.");
+
+ //kill receiver to limit load of signals
+ receiver->deleteLater();
+ receiver = 0;
+
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+ emit q->error(error);
+ _q_serviceDiscoveryFinished();
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp
index 7cb53253..5ba7ca13 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp
@@ -54,9 +54,9 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(const QBluetoothAddress &deviceAdapter)
-: error(QBluetoothServiceDiscoveryAgent::NoError), state(Inactive), deviceDiscoveryAgent(0),
+: error(QBluetoothServiceDiscoveryAgent::NoError), m_deviceAdapterAddress(deviceAdapter), state(Inactive), deviceDiscoveryAgent(0),
mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), singleDevice(false),
- manager(0), device(0), m_deviceAdapterAddress(deviceAdapter)
+ manager(0), adapter(0), device(0)
{
qRegisterMetaType<ServiceMap>("ServiceMap");
qDBusRegisterMetaType<ServiceMap>();
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_p.h b/src/bluetooth/qbluetoothservicediscoveryagent_p.h
index 0691b490..bfd6d954 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_p.h
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_p.h
@@ -70,6 +70,12 @@ QT_END_NAMESPACE
QT_BEGIN_NAMESPACE
class QBluetoothDeviceDiscoveryAgent;
+#ifdef QT_ANDROID_BLUETOOTH
+class ServiceDiscoveryBroadcastReceiver;
+class LocalDeviceBroadcastReceiver;
+#include <QtAndroidExtras/QAndroidJniObject>
+#include <QtBluetooth/QBluetoothLocalDevice>
+#endif
class QBluetoothServiceDiscoveryAgentPrivate
#ifdef QT_QNX_BLUETOOTH
@@ -97,7 +103,7 @@ public:
void stopServiceDiscovery();
void setDiscoveryState(DiscoveryState s) { state = s; }
- DiscoveryState discoveryState() { return state; }
+ inline DiscoveryState discoveryState() { return state; }
void setDiscoveryMode(QBluetoothServiceDiscoveryAgent::DiscoveryMode m) { mode = m; }
QBluetoothServiceDiscoveryAgent::DiscoveryMode DiscoveryMode() { return mode; }
@@ -111,6 +117,14 @@ public:
void _q_discoveredServices(QDBusPendingCallWatcher *watcher);
void _q_createdDevice(QDBusPendingCallWatcher *watcher);
#endif
+#ifdef QT_ANDROID_BLUETOOTH
+ void _q_processFetchedUuids(const QBluetoothAddress &address, const QList<QBluetoothUuid> &uuids);
+
+ void populateDiscoveredServices(const QBluetoothDeviceInfo &remoteDevice,
+ const QList<QBluetoothUuid> &uuids);
+ void _q_fetchUuidsTimeout();
+ void _q_hostModeStateChanged(QBluetoothLocalDevice::HostMode state);
+#endif
private:
void start(const QBluetoothAddress &address);
@@ -143,6 +157,7 @@ public:
QBluetoothAddress deviceAddress;
QList<QBluetoothServiceInfo> discoveredServices;
QList<QBluetoothDeviceInfo> discoveredDevices;
+ QBluetoothAddress m_deviceAdapterAddress;
private:
DiscoveryState state;
@@ -158,7 +173,14 @@ private:
OrgBluezManagerInterface *manager;
OrgBluezAdapterInterface *adapter;
OrgBluezDeviceInterface *device;
- QBluetoothAddress m_deviceAdapterAddress;
+#endif
+
+#ifdef QT_ANDROID_BLUETOOTH
+ ServiceDiscoveryBroadcastReceiver *receiver;
+ LocalDeviceBroadcastReceiver *localDeviceReceiver;
+
+ QAndroidJniObject btAdapter;
+ QMap<QBluetoothAddress,QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > > sdpCache;
#endif
protected:
diff --git a/src/bluetooth/qbluetoothserviceinfo_android.cpp b/src/bluetooth/qbluetoothserviceinfo_android.cpp
new file mode 100644
index 00000000..078554d0
--- /dev/null
+++ b/src/bluetooth/qbluetoothserviceinfo_android.cpp
@@ -0,0 +1,140 @@
+/***************************************************************************
+**
+** 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 <QtCore/QLoggingCategory>
+
+#include "qbluetoothhostinfo.h"
+#include "qbluetoothlocaldevice.h"
+#include "qbluetoothserviceinfo.h"
+#include "qbluetoothserviceinfo_p.h"
+#include "qbluetoothserver_p.h"
+#include "qbluetoothserver.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
+
+extern QHash<QBluetoothServerPrivate*, int> __fakeServerPorts;
+
+QBluetoothServiceInfoPrivate::QBluetoothServiceInfoPrivate()
+: registered(false)
+{
+}
+
+QBluetoothServiceInfoPrivate::~QBluetoothServiceInfoPrivate()
+{
+}
+
+bool QBluetoothServiceInfoPrivate::isRegistered() const
+{
+ return registered;
+}
+
+bool QBluetoothServiceInfoPrivate::unregisterService()
+{
+ if (!registered)
+ return false;
+
+ QBluetoothServerPrivate *sPriv = __fakeServerPorts.key(serverChannel());
+ if (!sPriv) {
+ //QBluetoothServer::close() was called without prior call to unregisterService().
+ //Now it is unregistered anyway.
+ registered = false;
+ return true;
+ }
+
+ bool result = sPriv->deactivateActiveListening();
+ if (!result)
+ return false;
+
+ registered = false;
+ return true;
+}
+
+bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress& localAdapter)
+{
+ const QList<QBluetoothHostInfo> localDevices = QBluetoothLocalDevice::allDevices();
+ if (!localDevices.count())
+ return false; //no Bluetooth device
+
+ if (!localAdapter.isNull()) {
+ bool found = false;
+ foreach (const QBluetoothHostInfo &hostInfo, localDevices) {
+ if (hostInfo.address() == localAdapter) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ qCWarning(QT_BT_ANDROID) << localAdapter.toString() << "is not a valid local Bt adapter";
+ return false;
+ }
+ }
+
+ //already registered on local adapter => nothing to do
+ if (registered)
+ return false;
+
+ if (protocolDescriptor(QBluetoothUuid::Rfcomm).isEmpty()) {
+ qCWarning(QT_BT_ANDROID) << Q_FUNC_INFO << "Only RFCOMM services can be registered on QNX";
+ return false;
+ }
+
+ QBluetoothServerPrivate *sPriv = __fakeServerPorts.key(serverChannel());
+ if (!sPriv)
+ return false;
+
+ //tell the server what service name and uuid our listener should have
+ //and start the real listener
+ bool result = sPriv->initiateActiveListening(
+ attributes.value(QBluetoothServiceInfo::ServiceId).value<QBluetoothUuid>(),
+ attributes.value(QBluetoothServiceInfo::ServiceName).toString());
+ if (!result) {
+ return false;
+ }
+
+
+ registered = true;
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/bluetooth/qbluetoothsocket.cpp b/src/bluetooth/qbluetoothsocket.cpp
index 2f2660d0..bd4b43fb 100644
--- a/src/bluetooth/qbluetoothsocket.cpp
+++ b/src/bluetooth/qbluetoothsocket.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Copyright (C) 2013 BlackBerry Limited. All rights reserved.
** Contact: http://www.qt-project.org/legal
**
@@ -66,6 +66,8 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_QNX)
\l {QBluetoothServiceInfo::RfcommProtocol}{RFCOMM}.
\l {QBluetoothServiceInfo::L2capProtocol}{L2CAP} is a low level datagram-oriented Bluetooth socket.
+ Android and BlackBerry do not support \l {QBluetoothServiceInfo::L2capProtocol}{L2CAP} for socket
+ connections.
\l {QBluetoothServiceInfo::RfcommProtocol}{RFCOMM} is a reliable, stream-oriented socket. RFCOMM
sockets emulate an RS-232 serial port.
@@ -106,6 +108,8 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_QNX)
\value NetworkError Attempt to read or write from socket returned an error
\value UnsupportedProtocolError The \l {QBluetoothServiceInfo::Protocol}{Protocol} is not
supported on this platform.
+ \value OperationError An operation was attempted while the socket was in a state
+ that did not permit it.
*/
/*!
@@ -192,7 +196,7 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_QNX)
Although some platforms may differ the socket must generally be connected to guarantee
the return of a valid port number.
- On BlackBerry, this feature is not supported at all and the function always returns 0.
+ On BlackBerry and Android, this feature is not supported and returns 0.
*/
/*!
@@ -211,7 +215,7 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_QNX)
\fn quint16 QBluetoothSocket::peerPort() const
Return the port number of the peer socket if available, otherwise returns 0.
- On BlackBerry, this feature is not supported.
+ On BlackBerry and Android, this feature is not supported.
*/
/*!
@@ -291,28 +295,37 @@ qint64 QBluetoothSocket::bytesToWrite() const
/*!
Attempts to connect to the service described by \a service.
- The socket is opened in the given \a openMode.
+ The socket is opened in the given \a openMode. The \l socketType() may change
+ depending on the protocol required by \a service.
- For BlueZ, the socket first enters ConnectingState and attempts to connect to the device providing
+ The socket first enters ConnectingState and attempts to connect to the device providing
\a service. If a connection is established, QBluetoothSocket enters ConnectedState and
emits connected().
- On QNX the service connection can be established directly using the UUID of the remote service.
+ At any point, the socket can emit error() to signal that an error occurred.
- At any point, the socket can emit error() to siganl that an error occurred.
+ Note that most platforms require a pairing prior to connecting to the remote device. Otherwise
+ the connection process may fail.
\sa state(), disconnectFromService()
*/
void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, OpenMode openMode)
{
Q_D(QBluetoothSocket);
+
+ if (state() != QBluetoothSocket::UnconnectedState) {
+ qCWarning(QT_BT) << "QBluetoothSocket::connectToService called on busy socket";
+ d->errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress");
+ setSocketError(QBluetoothSocket::OperationError);
+ return;
+ }
+
setOpenMode(openMode);
-#ifdef QT_QNX_BLUETOOTH
- if (socketType() != QBluetoothServiceInfo::RfcommProtocol) {
- d->socketError = QBluetoothSocket::UnsupportedProtocolError;
+#if defined(QT_QNX_BLUETOOTH) || defined(QT_ANDROID_BLUETOOTH)
+ if (!d->ensureNativeSocket(service.socketProtocol())) {
d->errorString = tr("Socket type not supported");
- Q_EMIT error(d->socketError);
+ setSocketError(QBluetoothSocket::UnsupportedProtocolError);
return;
}
d->connectToService(service.device().address(), service.serviceUuid(), openMode);
@@ -349,25 +362,38 @@ void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, Op
The socket is opened in the given \a openMode.
- For BlueZ, the socket first enters the ServiceLookupState and queries the connection parameters for
+ For BlueZ, the socket first enters the \l ServiceLookupState and queries the connection parameters for
\a uuid. If the service parameters are successfully retrieved the socket enters
ConnectingState, and attempts to connect to \a address. If a connection is established,
QBluetoothSocket enters Connected State and emits connected().
- On BlackBerry, the service connection can be established directly using the UUID of the remote service.
+ On BlackBerry and Android, the service connection can directly be established
+ using the UUID of the remote service. Therefore these platforms do not require
+ the \l ServiceLookupState and \l socketType() is always set to
+ \l QBluetoothServiceInfo::RfcommProtocol.
At any point, the socket can emit error() to signal that an error occurred.
+ Note that most platforms require a pairing prior to connecting to the remote device. Otherwise
+ the connection process may fail.
+
\sa state(), disconnectFromService()
*/
void QBluetoothSocket::connectToService(const QBluetoothAddress &address, const QBluetoothUuid &uuid, OpenMode openMode)
{
-#ifdef QT_QNX_BLUETOOTH
Q_D(QBluetoothSocket);
- if (socketType() != QBluetoothServiceInfo::RfcommProtocol) {
- d->socketError = QBluetoothSocket::UnsupportedProtocolError;
+
+ if (state() != QBluetoothSocket::UnconnectedState) {
+ qCWarning(QT_BT) << "QBluetoothSocket::connectToService called on busy socket";
+ d->errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress");
+ setSocketError(QBluetoothSocket::OperationError);
+ return;
+ }
+
+#if defined(QT_QNX_BLUETOOTH) || defined(QT_ANDROID_BLUETOOTH)
+ if (!d->ensureNativeSocket(QBluetoothServiceInfo::RfcommProtocol)) {
d->errorString = tr("Socket type not supported");
- Q_EMIT error(d->socketError);
+ setSocketError(QBluetoothSocket::UnsupportedProtocolError);
return;
}
d->connectToService(address, uuid, openMode);
@@ -390,30 +416,43 @@ void QBluetoothSocket::connectToService(const QBluetoothAddress &address, const
At any point, the socket can emit error() to signal that an error occurred.
- On BlackBerry, a connection to a service can not be established using a port. Calling this function
+ On BlackBerry and Android, a connection to a service can not be established using a port. Calling this function
will emit a \l {QBluetoothSocket::ServiceNotFoundError}{ServiceNotFoundError}
+ Note that most platforms require a pairing prior to connecting to the remote device. Otherwise
+ the connection process may fail.
+
\sa state(), disconnectFromService()
*/
void QBluetoothSocket::connectToService(const QBluetoothAddress &address, quint16 port, OpenMode openMode)
{
Q_D(QBluetoothSocket);
-#ifdef QT_QNX_BLUETOOTH
+#if defined(QT_QNX_BLUETOOTH) || defined(QT_ANDROID_BLUETOOTH)
Q_UNUSED(port);
Q_UNUSED(openMode);
Q_UNUSED(address);
- d->socketError = QBluetoothSocket::ServiceNotFoundError;
- d->errorString = tr("Connecting to port is not supported on QNX");
- Q_EMIT error(d->socketError);
- qCWarning(QT_BT_QNX) << "Connecting to port is not supported";
+ d->errorString = tr("Connecting to port is not supported");
+ setSocketError(QBluetoothSocket::ServiceNotFoundError);
+ qCWarning(QT_BT) << "Connecting to port is not supported";
#else
+ if (state() != QBluetoothSocket::UnconnectedState) {
+ qCWarning(QT_BT) << "QBluetoothSocket::connectToService called on busy socket";
+ d->errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress");
+ setSocketError(QBluetoothSocket::OperationError);
+ return;
+ }
+
setOpenMode(openMode);
d->connectToService(address, port, openMode);
#endif
}
/*!
- Returns the socket type.
+ Returns the socket type. The socket automatically adjusts to the protocol
+ offered by the remote service.
+
+ Blackberry and Android only support \l{QBluetoothServiceInfo::RfcommProtocol}{RFCOMM}
+ based sockets.
*/
QBluetoothServiceInfo::Protocol QBluetoothSocket::socketType() const
{
@@ -555,6 +594,9 @@ void QBluetoothSocket::discoveryFinished()
void QBluetoothSocket::abort()
{
+ if (state() == UnconnectedState)
+ return;
+
Q_D(QBluetoothSocket);
d->abort();
setSocketState(QBluetoothSocket::UnconnectedState);
@@ -562,9 +604,7 @@ void QBluetoothSocket::abort()
void QBluetoothSocket::disconnectFromService()
{
- // TODO: is this all we need to do?
- Q_D(QBluetoothSocket);
- d->close();
+ close();
}
QString QBluetoothSocket::localName() const
@@ -617,6 +657,9 @@ qint64 QBluetoothSocket::readData(char *data, qint64 maxSize)
void QBluetoothSocket::close()
{
+ if (state() == UnconnectedState)
+ return;
+
Q_D(QBluetoothSocket);
setSocketState(ClosingState);
diff --git a/src/bluetooth/qbluetoothsocket.h b/src/bluetooth/qbluetoothsocket.h
index 5a21d551..0cc765cd 100644
--- a/src/bluetooth/qbluetoothsocket.h
+++ b/src/bluetooth/qbluetoothsocket.h
@@ -78,11 +78,13 @@ public:
enum SocketError {
NoSocketError = -2,
- UnknownSocketError = QAbstractSocket::UnknownSocketError,
- HostNotFoundError = QAbstractSocket::HostNotFoundError,
- ServiceNotFoundError = QAbstractSocket::SocketAddressNotAvailableError,
- NetworkError = QAbstractSocket::NetworkError,
- UnsupportedProtocolError
+ UnknownSocketError = QAbstractSocket::UnknownSocketError, //-1
+ HostNotFoundError = QAbstractSocket::HostNotFoundError, //2
+ ServiceNotFoundError = QAbstractSocket::SocketAddressNotAvailableError, //9
+ NetworkError = QAbstractSocket::NetworkError, //7
+ UnsupportedProtocolError = 8,
+ OperationError = QAbstractSocket::OperationError //19
+ //New enums (independent of QAbstractSocket) should be added from 100 onwards
};
explicit QBluetoothSocket(QBluetoothServiceInfo::Protocol socketType, QObject *parent = 0); // create socket of type socketType
diff --git a/src/bluetooth/qbluetoothsocket_android.cpp b/src/bluetooth/qbluetoothsocket_android.cpp
new file mode 100644
index 00000000..0b6fd9c6
--- /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").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
diff --git a/src/bluetooth/qbluetoothsocket_p.h b/src/bluetooth/qbluetoothsocket_p.h
index 08a6ec86..95e67e51 100644
--- a/src/bluetooth/qbluetoothsocket_p.h
+++ b/src/bluetooth/qbluetoothsocket_p.h
@@ -47,6 +47,11 @@
#ifdef QT_QNX_BLUETOOTH
#include "qnx/ppshelpers_p.h"
#endif
+#ifdef QT_ANDROID_BLUETOOTH
+#include <QtAndroidExtras/QAndroidJniObject>
+#include "android/inputstreamthread_p.h"
+#include <jni.h>
+#endif
#ifndef QPRIVATELINEARBUFFER_BUFFERSIZE
#define QPRIVATELINEARBUFFER_BUFFERSIZE Q_INT64_C(16384)
@@ -74,7 +79,7 @@ class QBluetoothSocket;
class QBluetoothServiceDiscoveryAgent;
class QBluetoothSocketPrivate
-#ifdef QT_QNX_BLUETOOTH
+#if defined(QT_QNX_BLUETOOTH) || defined(QT_ANDROID_BLUETOOTH)
: public QObject
{
Q_OBJECT
@@ -87,12 +92,16 @@ public:
QBluetoothSocketPrivate();
~QBluetoothSocketPrivate();
-//On qnx we connect using the uuid not the port
-#ifdef QT_QNX_BLUETOOTH
+//On QNX and Android we connect using the uuid not the port
+#if defined(QT_QNX_BLUETOOTH) || defined(QT_ANDROID_BLUETOOTH)
void connectToService(const QBluetoothAddress &address, QBluetoothUuid uuid, QIODevice::OpenMode openMode);
#else
void connectToService(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode);
#endif
+#ifdef QT_ANDROID_BLUETOOTH
+ void connectToServiceConc(const QBluetoothAddress &address, const QBluetoothUuid &uuid, QIODevice::OpenMode openMode);
+#endif
+
bool ensureNativeSocket(QBluetoothServiceInfo::Protocol type);
@@ -114,6 +123,11 @@ public:
qint64 writeData(const char *data, qint64 maxSize);
qint64 readData(char *data, qint64 maxSize);
+#ifdef QT_ANDROID_BLUETOOTH
+ bool setSocketDescriptor(const QAndroidJniObject &socket, QBluetoothServiceInfo::Protocol socketType,
+ QBluetoothSocket::SocketState socketState = QBluetoothSocket::ConnectedState,
+ QBluetoothSocket::OpenMode openMode = QBluetoothSocket::ReadWrite);
+#endif
bool setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType,
QBluetoothSocket::SocketState socketState = QBluetoothSocket::ConnectedState,
QBluetoothSocket::OpenMode openMode = QBluetoothSocket::ReadWrite);
@@ -146,6 +160,19 @@ public:
void _q_serviceDiscovered(const QBluetoothServiceInfo &service);
void _q_discoveryFinished();
+#ifdef QT_ANDROID_BLUETOOTH
+ QAndroidJniObject adapter;
+ QAndroidJniObject socketObject;
+ QAndroidJniObject remoteDevice;
+ QAndroidJniObject inputStream;
+ QAndroidJniObject outputStream;
+ InputStreamThread *inputThread;
+
+private Q_SLOTS:
+ void inputThreadError();
+
+#endif
+
protected:
QBluetoothSocket *q_ptr;
diff --git a/src/bluetooth/qbluetoothsocket_qnx.cpp b/src/bluetooth/qbluetoothsocket_qnx.cpp
index fc609919..81c46dd7 100644
--- a/src/bluetooth/qbluetoothsocket_qnx.cpp
+++ b/src/bluetooth/qbluetoothsocket_qnx.cpp
@@ -69,6 +69,9 @@ QBluetoothSocketPrivate::~QBluetoothSocketPrivate()
bool QBluetoothSocketPrivate::ensureNativeSocket(QBluetoothServiceInfo::Protocol type)
{
socketType = type;
+ if (socketType == QBluetoothServiceInfo::RfcommProtocol)
+ return true;
+
return false;
}
@@ -251,7 +254,7 @@ void QBluetoothSocketPrivate::close()
abort();
}
-bool QBluetoothSocketPrivate::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType,
+bool QBluetoothSocketPrivate::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType_,
QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode)
{
Q_Q(QBluetoothSocket);
@@ -261,7 +264,7 @@ bool QBluetoothSocketPrivate::setSocketDescriptor(int socketDescriptor, QBluetoo
connectWriteNotifier = 0;
socket = socketDescriptor;
- socketType = socketType;
+ socketType = socketType_;
// ensure that O_NONBLOCK is set on new connections.
int flags = fcntl(socket, F_GETFL, 0);
diff --git a/src/bluetooth/qbluetoothtransfermanager.cpp b/src/bluetooth/qbluetoothtransfermanager.cpp
index ffb4862c..6e1d7f83 100644
--- a/src/bluetooth/qbluetoothtransfermanager.cpp
+++ b/src/bluetooth/qbluetoothtransfermanager.cpp
@@ -58,6 +58,8 @@ QT_BEGIN_NAMESPACE
using Object Push Profile (OPP).
QBluetoothTransferManager uses OBEX to send put commands to remote devices.
+
+ Note that this API is not currently supported on Android.
*/
/*!
@@ -65,6 +67,8 @@ QT_BEGIN_NAMESPACE
Sends the contents of \a data to the remote device identified by \a request, and returns a new
QBluetoothTransferReply that can be used to track the request's progress.
+
+ If the platform does not support the Object Push profile, this function will return \c 0.
*/