summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--src/android/bluetooth/bluetooth.pri5
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java28
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer.java349
-rw-r--r--src/bluetooth/android/jni_android.cpp12
-rw-r--r--src/bluetooth/android/lowenergynotificationhub.cpp30
-rw-r--r--src/bluetooth/android/lowenergynotificationhub_p.h5
-rw-r--r--src/bluetooth/bluetooth.pro11
-rw-r--r--src/bluetooth/osx/osxbtledeviceinquiry.mm9
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp4
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_p.cpp8
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_p.h24
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent.cpp2
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_p.h21
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp604
-rw-r--r--src/bluetooth/qbluetoothsocket.cpp27
-rw-r--r--src/bluetooth/qbluetoothsocket_p.h42
-rw-r--r--src/bluetooth/qbluetoothsocket_winrt.cpp644
-rw-r--r--src/bluetooth/qlowenergyadvertisingdata.cpp9
-rw-r--r--src/bluetooth/qlowenergyconnectionparameters.cpp29
-rw-r--r--src/bluetooth/qlowenergycontroller.cpp37
-rw-r--r--src/bluetooth/qlowenergycontroller_android.cpp536
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h11
-rw-r--r--src/bluetooth/qlowenergyserviceprivate_p.h10
-rw-r--r--src/nfc/nfc.pro7
-rw-r--r--src/nfc/qnearfieldtarget.cpp86
-rw-r--r--src/nfc/qnearfieldtarget.h13
-rw-r--r--src/nfc/qnearfieldtarget_android.cpp294
-rw-r--r--src/nfc/qnearfieldtarget_android_p.cpp71
-rw-r--r--src/nfc/qnearfieldtarget_android_p.h9
-rw-r--r--src/nfc/qnearfieldtarget_emulator.cpp15
-rw-r--r--src/nfc/qnearfieldtarget_neard_p.cpp67
-rw-r--r--src/nfc/qnearfieldtarget_p.cpp68
-rw-r--r--src/nfc/qnearfieldtarget_p.h13
-rw-r--r--src/nfc/targetemulator.cpp13
35 files changed, 2922 insertions, 193 deletions
diff --git a/.qmake.conf b/.qmake.conf
index fec66b73..b1c22d3b 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -1,3 +1,3 @@
load(qt_build_config)
-MODULE_VERSION = 5.8.1
+MODULE_VERSION = 5.9.0
diff --git a/src/android/bluetooth/bluetooth.pri b/src/android/bluetooth/bluetooth.pri
index 16d3b612..fa811ac1 100644
--- a/src/android/bluetooth/bluetooth.pri
+++ b/src/android/bluetooth/bluetooth.pri
@@ -1,6 +1,6 @@
CONFIG += java
DESTDIR = $$[QT_INSTALL_PREFIX/get]/jar
-API_VERSION = android-18
+API_VERSION = android-21
PATHPREFIX = $$PWD/src/org/qtproject/qt5/android/bluetooth
@@ -9,7 +9,8 @@ JAVASOURCES += \
$$PATHPREFIX/QtBluetoothBroadcastReceiver.java \
$$PATHPREFIX/QtBluetoothSocketServer.java \
$$PATHPREFIX/QtBluetoothInputStreamThread.java \
- $$PATHPREFIX/QtBluetoothLE.java
+ $$PATHPREFIX/QtBluetoothLE.java \
+ $$PATHPREFIX/QtBluetoothLEServer.java
# install
target.path = $$[QT_INSTALL_PREFIX]/jar
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
index 5c336dc0..cb7b2dc9 100644
--- a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
+++ b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java
@@ -53,6 +53,7 @@ import android.os.Looper;
import android.util.Log;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicInteger;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Hashtable;
@@ -61,6 +62,7 @@ import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
+
public class QtBluetoothLE {
private static final String TAG = "QtBluetoothGatt";
private final BluetoothAdapter mBluetoothAdapter;
@@ -1315,6 +1317,32 @@ public class QtBluetoothLE {
return modifiedHandle;
}
+ // Directly called from public Qt API
+ public boolean requestConnectionUpdatePriority(double minimalInterval)
+ {
+ if (mBluetoothGatt == null)
+ return false;
+
+ try {
+ //Android API v21
+ Method connectionUpdateMethod = mBluetoothGatt.getClass().getDeclaredMethod(
+ "requestConnectionPriority", int.class);
+ if (connectionUpdateMethod == null)
+ return false;
+
+ int requestPriority = 0; // BluetoothGatt.CONNECTION_PRIORITY_BALANCED
+ if (minimalInterval < 30)
+ requestPriority = 1; // BluetoothGatt.CONNECTION_PRIORITY_HIGH
+ else if (minimalInterval > 100)
+ requestPriority = 2; //BluetoothGatt/CONNECTION_PRIORITY_LOW_POWER
+
+ Object result = connectionUpdateMethod.invoke(mBluetoothGatt, requestPriority);
+ return (Boolean) result;
+ } catch (Exception ex) {
+ return false;
+ }
+ }
+
public native void leConnectionStateChange(long qtObject, int wasErrorTransition, int newState);
public native void leServicesDiscovered(long qtObject, int errorCode, String uuidList);
public native void leServiceDetailDiscoveryFinished(long qtObject, final String serviceUuid,
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer.java
new file mode 100644
index 00000000..910143dd
--- /dev/null
+++ b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer.java
@@ -0,0 +1,349 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2016 The Qt Company Ltd.
+ ** Contact: https://www.qt.io/licensing/
+ **
+ ** This file is part of the QtBluetooth module of the Qt Toolkit.
+ **
+ ** $QT_BEGIN_LICENSE:LGPL$
+ ** Commercial License Usage
+ ** Licensees holding valid commercial Qt licenses may use this file in
+ ** accordance with the commercial license agreement provided with the
+ ** Software or, alternatively, in accordance with the terms contained in
+ ** a written agreement between you and The Qt Company. For licensing terms
+ ** and conditions see https://www.qt.io/terms-conditions. For further
+ ** information use the contact form at https://www.qt.io/contact-us.
+ **
+ ** GNU Lesser General Public License Usage
+ ** Alternatively, this file may be used under the terms of the GNU Lesser
+ ** General Public License version 3 as published by the Free Software
+ ** Foundation and appearing in the file LICENSE.LGPL3 included in the
+ ** packaging of this file. Please review the following information to
+ ** ensure the GNU Lesser General Public License version 3 requirements
+ ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+ **
+ ** GNU General Public License Usage
+ ** Alternatively, this file may be used under the terms of the GNU
+ ** General Public License version 2.0 or (at your option) the GNU General
+ ** Public license version 3 or any later version approved by the KDE Free
+ ** Qt Foundation. The licenses are as published by the Free Software
+ ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+ ** included in the packaging of this file. Please review the following
+ ** information to ensure the GNU General Public License requirements will
+ ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+ ** https://www.gnu.org/licenses/gpl-3.0.html.
+ **
+ ** $QT_END_LICENSE$
+ **
+ ****************************************************************************/
+
+package org.qtproject.qt5.android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseData.Builder;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+public class QtBluetoothLEServer {
+ private static final String TAG = "QtBluetoothGattServer";
+
+ /* Pointer to the Qt object that "owns" the Java object */
+ @SuppressWarnings({"CanBeFinal", "WeakerAccess"})
+ long qtObject = 0;
+ @SuppressWarnings("WeakerAccess")
+
+ private Context qtContext = null;
+
+ // Bluetooth members
+ private final BluetoothAdapter mBluetoothAdapter;
+ private BluetoothGattServer mGattServer = null;
+ private BluetoothLeAdvertiser mLeAdvertiser = null;
+
+ public QtBluetoothLEServer(Context context)
+ {
+ qtContext = context;
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ if (mBluetoothAdapter == null || qtContext == null) {
+ Log.w(TAG, "Missing Bluetooth adapter or Qt context. Peripheral role disabled.");
+ return;
+ }
+
+ BluetoothManager manager = (BluetoothManager) qtContext.getSystemService(Context.BLUETOOTH_SERVICE);
+ if (manager == null) {
+ Log.w(TAG, "Bluetooth service not available.");
+ return;
+ }
+
+ mGattServer = manager.openGattServer(qtContext, mGattServerListener);
+ mLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
+
+ if (!mBluetoothAdapter.isMultipleAdvertisementSupported())
+ Log.w(TAG, "Device does not support Bluetooth Low Energy advertisement.");
+ else
+ Log.w(TAG, "Let's do BTLE Peripheral.");
+ }
+
+ /*
+ * Call back handler for the Gatt Server.
+ */
+ private BluetoothGattServerCallback mGattServerListener = new BluetoothGattServerCallback()
+ {
+ @Override
+ public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+ Log.w(TAG, "Our gatt server connection state changed, new state: " + Integer.toString(newState));
+ super.onConnectionStateChange(device, status, newState);
+
+ int qtControllerState = 0;
+ switch (newState) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ qtControllerState = 0; // QLowEnergyController::UnconnectedState
+ break;
+ case BluetoothProfile.STATE_CONNECTED:
+ qtControllerState = 2; // QLowEnergyController::ConnectedState
+ break;
+ }
+
+ int qtErrorCode;
+ switch (status) {
+ case BluetoothGatt.GATT_SUCCESS:
+ qtErrorCode = 0; break;
+ default:
+ Log.w(TAG, "Unhandled error code on peripheral connectionStateChanged: " + status + " " + newState);
+ qtErrorCode = status;
+ break;
+ }
+
+ leServerConnectionStateChange(qtObject, qtErrorCode, qtControllerState);
+ }
+
+ @Override
+ public void onServiceAdded(int status, BluetoothGattService service) {
+ super.onServiceAdded(status, service);
+ }
+
+ @Override
+ public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic)
+ {
+ byte[] dataArray;
+ try {
+ dataArray = Arrays.copyOfRange(characteristic.getValue(), offset, characteristic.getValue().length);
+ mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, dataArray);
+ } catch (Exception ex) {
+ Log.w(TAG, "onCharacteristicReadRequest: " + requestId + " " + offset + " " + characteristic.getValue().length);
+ ex.printStackTrace();
+ mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, offset, null);
+ }
+
+ super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
+ }
+
+ @Override
+ public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic,
+ boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)
+ {
+ Log.w(TAG, "onCharacteristicWriteRequest");
+ int resultStatus = BluetoothGatt.GATT_SUCCESS;
+
+ if (!preparedWrite) { // regular write
+ if (offset == 0) {
+ characteristic.setValue(value);
+ } else {
+ // This should not really happen as per Bluetooth spec
+ Log.w(TAG, "onCharacteristicWriteRequest: !preparedWrite, offset " + offset + ", Not supported");
+ resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED;
+ }
+
+
+ } else {
+ Log.w(TAG, "onCharacteristicWriteRequest: preparedWrite, offset " + offset + ", Not supported");
+ resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED;
+
+ // TODO we need to record all requests and execute them in one go once onExecuteWrite() is received
+ // we use a queue to remember the pending requests
+ // TODO we are ignoring the device identificator for now -> Bluetooth spec requires a queue per device
+ }
+
+
+ if (responseNeeded)
+ mGattServer.sendResponse(device, requestId, resultStatus, offset, value);
+
+ super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
+ }
+
+ @Override
+ public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor)
+ {
+ byte[] dataArray;
+ try {
+ dataArray = Arrays.copyOfRange(descriptor.getValue(), offset, descriptor.getValue().length);
+ mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, dataArray);
+ } catch (Exception ex) {
+ Log.w(TAG, "onDescriptorReadRequest: " + requestId + " " + offset + " " + descriptor.getValue().length);
+ ex.printStackTrace();
+ mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, offset, null);
+ }
+
+ super.onDescriptorReadRequest(device, requestId, offset, descriptor);
+ }
+
+ @Override
+ public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor,
+ boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)
+ {
+ int resultStatus = BluetoothGatt.GATT_SUCCESS;
+ if (!preparedWrite) { // regular write
+ if (offset == 0) {
+ descriptor.setValue(value);
+ } else {
+ // This should not really happen as per Bluetooth spec
+ Log.w(TAG, "onDescriptorWriteRequest: !preparedWrite, offset " + offset + ", Not supported");
+ resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED;
+ }
+
+
+ } else {
+ Log.w(TAG, "onDescriptorWriteRequest: preparedWrite, offset " + offset + ", Not supported");
+ resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED;
+ // TODO we need to record all requests and execute them in one go once onExecuteWrite() is received
+ // we use a queue to remember the pending requests
+ // TODO we are ignoring the device identificator for now -> Bluetooth spec requires a queue per device
+ }
+
+
+ if (responseNeeded)
+ mGattServer.sendResponse(device, requestId, resultStatus, offset, value);
+
+ super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
+ }
+
+ @Override
+ public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute)
+ {
+ // TODO not yet implemented -> return proper GATT error for it
+ mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, 0, null);
+
+ super.onExecuteWrite(device, requestId, execute);
+ }
+
+ @Override
+ public void onNotificationSent(BluetoothDevice device, int status) {
+ super.onNotificationSent(device, status);
+ }
+
+ // MTU change disabled since it requires API level 22. Right now we only enforce lvl 21
+// @Override
+// public void onMtuChanged(BluetoothDevice device, int mtu) {
+// super.onMtuChanged(device, mtu);
+// }
+ };
+
+ public boolean connectServer()
+ {
+ if (mGattServer == null)
+ return false;
+
+ return true;
+ }
+
+ public void disconnectServer()
+ {
+ if (mGattServer == null)
+ return;
+
+ mGattServer.close();
+ }
+
+ public boolean startAdvertising(AdvertiseData advertiseData,
+ AdvertiseData scanResponse,
+ AdvertiseSettings settings)
+ {
+ if (mLeAdvertiser == null)
+ return false;
+
+ connectServer();
+
+ Log.w(TAG, "Starting to advertise.");
+ mLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseListener);
+
+ return true;
+ }
+
+ public void stopAdvertising()
+ {
+ if (mLeAdvertiser == null)
+ return;
+
+ mLeAdvertiser.stopAdvertising(mAdvertiseListener);
+ Log.w(TAG, "Advertisement stopped.");
+ }
+
+ public void addService(BluetoothGattService service)
+ {
+ if (mGattServer == null)
+ return;
+
+ mGattServer.addService(service);
+ }
+
+ /*
+ * Call back handler for Advertisement requests.
+ */
+ private AdvertiseCallback mAdvertiseListener = new AdvertiseCallback()
+ {
+ @Override
+ public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+ super.onStartSuccess(settingsInEffect);
+ }
+
+ @Override
+ public void onStartFailure(int errorCode) {
+ Log.e(TAG, "Advertising failure: " + errorCode);
+ super.onStartFailure(errorCode);
+
+ // changing errorCode here implies changes to errorCode handling on Qt side
+ int qtErrorCode = 0;
+ switch (errorCode) {
+ case AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED:
+ return; // ignore -> noop
+ case AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE:
+ qtErrorCode = 1;
+ break;
+ case AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED:
+ qtErrorCode = 2;
+ break;
+ default: // default maps to internal error
+ case AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR:
+ qtErrorCode = 3;
+ break;
+ case AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS:
+ qtErrorCode = 4;
+ break;
+ }
+
+ if (qtErrorCode > 0)
+ leServerAdvertisementError(qtObject, qtErrorCode);
+ }
+ };
+
+ public native void leServerConnectionStateChange(long qtObject, int errorCode, int newState);
+ public native void leServerAdvertisementError(long qtObject, int status);
+
+}
diff --git a/src/bluetooth/android/jni_android.cpp b/src/bluetooth/android/jni_android.cpp
index ae07608a..e63854bd 100644
--- a/src/bluetooth/android/jni_android.cpp
+++ b/src/bluetooth/android/jni_android.cpp
@@ -227,6 +227,13 @@ static JNINativeMethod methods_le[] = {
(void *) LowEnergyNotificationHub::lowEnergy_serviceError},
};
+static JNINativeMethod methods_leServer[] = {
+ {"leServerConnectionStateChange", "(JII)V",
+ (void *) LowEnergyNotificationHub::lowEnergy_connectionChange},
+ {"leServerAdvertisementError", "(JI)V",
+ (void *) LowEnergyNotificationHub::lowEnergy_advertisementError},
+};
+
static JNINativeMethod methods_server[] = {
{"errorOccurred", "(JI)V",
(void *) QtBluetoothSocketServer_errorOccurred},
@@ -267,6 +274,11 @@ static bool registerNatives(JNIEnv *env)
return false;
}
+ FIND_AND_CHECK_CLASS("org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer");
+ if (env->RegisterNatives(clazz, methods_leServer, sizeof(methods_leServer) / sizeof(methods_leServer[0])) < 0) {
+ __android_log_print(ANDROID_LOG_FATAL, logTag, "RegisterNatives for QBLuetoothLEServer failed");
+ return false;
+ }
FIND_AND_CHECK_CLASS("org/qtproject/qt5/android/bluetooth/QtBluetoothSocketServer");
if (env->RegisterNatives(clazz, methods_server, sizeof(methods_server) / sizeof(methods_server[0])) < 0) {
diff --git a/src/bluetooth/android/lowenergynotificationhub.cpp b/src/bluetooth/android/lowenergynotificationhub.cpp
index 2a6cdce3..d6e0a4f1 100644
--- a/src/bluetooth/android/lowenergynotificationhub.cpp
+++ b/src/bluetooth/android/lowenergynotificationhub.cpp
@@ -53,18 +53,25 @@ QReadWriteLock LowEnergyNotificationHub::lock;
Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
-LowEnergyNotificationHub::LowEnergyNotificationHub(
- const QBluetoothAddress &remote, QObject *parent)
+LowEnergyNotificationHub::LowEnergyNotificationHub(const QBluetoothAddress &remote,
+ bool isPeripheral, QObject *parent)
: QObject(parent), javaToCtoken(0)
{
QAndroidJniEnvironment env;
- const QAndroidJniObject address =
+
+ if (isPeripheral) {
+ qCDebug(QT_BT_ANDROID) << "Creating Android Peripheral/Server support for BTLE";
+ jBluetoothLe = QAndroidJniObject("org/qtproject/qt5/android/bluetooth/QtBluetoothLEServer",
+ "(Landroid/content/Context;)V", QtAndroidPrivate::context());
+ } else {
+ qCDebug(QT_BT_ANDROID) << "Creating Android Central/Client support for BTLE";
+ const QAndroidJniObject address =
QAndroidJniObject::fromString(remote.toString());
- jBluetoothLe = QAndroidJniObject("org/qtproject/qt5/android/bluetooth/QtBluetoothLE",
+ jBluetoothLe = QAndroidJniObject("org/qtproject/qt5/android/bluetooth/QtBluetoothLE",
"(Ljava/lang/String;Landroid/content/Context;)V",
address.object<jstring>(),
QtAndroidPrivate::activity() ? QtAndroidPrivate::activity() : QtAndroidPrivate::service());
-
+ }
if (env->ExceptionCheck() || !jBluetoothLe.isValid()) {
env->ExceptionDescribe();
@@ -302,4 +309,17 @@ void LowEnergyNotificationHub::lowEnergy_serviceError(
(QLowEnergyService::ServiceError)errorCode));
}
+void LowEnergyNotificationHub::lowEnergy_advertisementError(
+ JNIEnv *, jobject, jlong qtObject, jint status)
+{
+ lock.lockForRead();
+ LowEnergyNotificationHub *hub = hubMap()->value(qtObject);
+ lock.unlock();
+ if (!hub)
+ return;
+
+ QMetaObject::invokeMethod(hub, "advertisementError", Qt::QueuedConnection,
+ Q_ARG(int, status));
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/android/lowenergynotificationhub_p.h b/src/bluetooth/android/lowenergynotificationhub_p.h
index a957aac0..4efbd6de 100644
--- a/src/bluetooth/android/lowenergynotificationhub_p.h
+++ b/src/bluetooth/android/lowenergynotificationhub_p.h
@@ -67,7 +67,7 @@ class LowEnergyNotificationHub : public QObject
{
Q_OBJECT
public:
- explicit LowEnergyNotificationHub(const QBluetoothAddress &remote,
+ explicit LowEnergyNotificationHub(const QBluetoothAddress &remote, bool isPeripheral,
QObject *parent = 0);
~LowEnergyNotificationHub();
@@ -95,6 +95,8 @@ public:
jint charHandle, jbyteArray data);
static void lowEnergy_serviceError(JNIEnv *, jobject, jlong qtObject,
jint attributeHandle, int errorCode);
+ static void lowEnergy_advertisementError(JNIEnv *, jobject, jlong qtObject,
+ jint status);
QAndroidJniObject javaObject()
{
@@ -118,6 +120,7 @@ signals:
QLowEnergyService::ServiceError errorCode);
void characteristicChanged(int charHandle, const QByteArray &data);
void serviceError(int attributeHandle, QLowEnergyService::ServiceError errorCode);
+ void advertisementError(int status);
public slots:
private:
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro
index bf2dce74..20f89b8e 100644
--- a/src/bluetooth/bluetooth.pro
+++ b/src/bluetooth/bluetooth.pro
@@ -201,10 +201,17 @@ qtConfig(bluez):qtHaveModule(dbus) {
qbluetoothdevicediscoveryagent_winrt.cpp \
qbluetoothlocaldevice_p.cpp \
qbluetoothserver_p.cpp \
- qbluetoothservicediscoveryagent_p.cpp \
+ qbluetoothservicediscoveryagent_winrt.cpp \
qbluetoothserviceinfo_p.cpp \
- qbluetoothsocket_p.cpp \
+ qbluetoothsocket_winrt.cpp \
qlowenergycontroller_winrt.cpp
+
+ WINRT_SDK_VERSION_STRING = $$(UCRTVersion)
+ WINRT_SDK_VERSION = $$member($$list($$split(WINRT_SDK_VERSION_STRING, .)), 2)
+ lessThan(WINRT_SDK_VERSION, 14393) {
+ DEFINES += QT_WINRT_LIMITED_SERVICEDISCOVERY
+ DEFINES += QT_UCRTVERSION=$$WINRT_SDK_VERSION
+ }
} else {
message("Unsupported Bluetooth platform, will not build a working QtBluetooth library.")
message("Either no Qt D-Bus found or no BlueZ headers available.")
diff --git a/src/bluetooth/osx/osxbtledeviceinquiry.mm b/src/bluetooth/osx/osxbtledeviceinquiry.mm
index 2dabd0c1..3ce66d49 100644
--- a/src/bluetooth/osx/osxbtledeviceinquiry.mm
+++ b/src/bluetooth/osx/osxbtledeviceinquiry.mm
@@ -364,13 +364,10 @@ QT_USE_NAMESPACE
return;
}
- QString name;
- if (peripheral.name)
- name = QString::fromNSString(peripheral.name);
-
const AdvertisementData qtAdvData(advertisementData);
- if (!name.size()) // Probably, it's not possible to have one and not the other.
- name = qtAdvData.localName;
+ QString name(qtAdvData.localName);
+ if (!name.size() && peripheral.name)
+ name = QString::fromNSString(peripheral.name);
// TODO: fix 'classOfDevice' (0 for now).
QBluetoothDeviceInfo newDeviceInfo(deviceUuid, name, 0);
diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
index 7813bfdc..28d05ec1 100644
--- a/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
+++ b/src/bluetooth/qbluetoothdevicediscoveryagent_winrt.cpp
@@ -436,6 +436,10 @@ HRESULT QWinRTBluetoothDeviceDiscoveryWorker::onBluetoothLEDeviceFound(ComPtr<IB
ComPtr<IDeviceInformation> deviceInfo;
hr = device2->get_DeviceInformation(&deviceInfo);
Q_ASSERT_SUCCEEDED(hr);
+ if (!deviceInfo) {
+ qCDebug(QT_BT_WINRT) << "onBluetoothLEDeviceFound: Could not obtain device information";
+ return S_OK;
+ }
ComPtr<IDeviceInformation2> deviceInfo2;
hr = deviceInfo.As(&deviceInfo2);
Q_ASSERT_SUCCEEDED(hr);
diff --git a/src/bluetooth/qbluetoothlocaldevice_p.cpp b/src/bluetooth/qbluetoothlocaldevice_p.cpp
index e9177f39..28e787c7 100644
--- a/src/bluetooth/qbluetoothlocaldevice_p.cpp
+++ b/src/bluetooth/qbluetoothlocaldevice_p.cpp
@@ -85,7 +85,11 @@ void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode)
QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const
{
+#ifdef QT_WINRT_BLUETOOTH
+ return HostConnectable;
+#else
return HostPoweredOff;
+#endif
}
QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
@@ -112,7 +116,11 @@ QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(
const QBluetoothAddress &address) const
{
Q_UNUSED(address);
+#ifdef QT_WINRT_BLUETOOTH
+ return Paired;
+#else
return Unpaired;
+#endif
}
void QBluetoothLocalDevice::pairingConfirmation(bool confirmation)
diff --git a/src/bluetooth/qbluetoothlocaldevice_p.h b/src/bluetooth/qbluetoothlocaldevice_p.h
index 1848a073..ca6a119b 100644
--- a/src/bluetooth/qbluetoothlocaldevice_p.h
+++ b/src/bluetooth/qbluetoothlocaldevice_p.h
@@ -206,6 +206,30 @@ private:
void initializeAdapter();
void initializeAdapterBluez5();
};
+#elif defined(QT_WINRT_BLUETOOTH)
+class QBluetoothLocalDevicePrivate : public QObject
+{
+public:
+ QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q,
+ QBluetoothAddress localAddress = QBluetoothAddress())
+ : q_ptr(q)
+ {
+ Q_UNUSED(localAddress);
+ }
+
+ ~QBluetoothLocalDevicePrivate()
+ {
+ }
+
+
+ bool isValid() const
+ {
+ return true;
+ }
+
+private:
+ QBluetoothLocalDevice *q_ptr;
+};
#elif !defined(QT_OSX_BLUETOOTH)
class QBluetoothLocalDevicePrivate : public QObject
{
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent.cpp b/src/bluetooth/qbluetoothservicediscoveryagent.cpp
index cd28cc25..ed9e9d63 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent.cpp
+++ b/src/bluetooth/qbluetoothservicediscoveryagent.cpp
@@ -164,6 +164,8 @@ QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(QObject *parent
\l InvalidBluetoothAdapterError. Therefore it is recommended to test the error flag immediately after
using this constructor.
+ \note On WinRT the passed adapter address will be ignored.
+
\sa error()
*/
QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(const QBluetoothAddress &deviceAdapter, QObject *parent)
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_p.h b/src/bluetooth/qbluetoothservicediscoveryagent_p.h
index 47231d8f..ef5db720 100644
--- a/src/bluetooth/qbluetoothservicediscoveryagent_p.h
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_p.h
@@ -72,6 +72,10 @@ class QXmlStreamReader;
QT_END_NAMESPACE
#endif
+#ifdef QT_WINRT_BLUETOOTH
+class QWinRTBluetoothServiceDiscoveryWorker;
+#endif
+
QT_BEGIN_NAMESPACE
class QBluetoothDeviceDiscoveryAgent;
@@ -83,7 +87,13 @@ class LocalDeviceBroadcastReceiver;
#endif
class QBluetoothServiceDiscoveryAgentPrivate
+#if defined QT_WINRT_BLUETOOTH
+ : public QObject
{
+ Q_OBJECT
+#else
+{
+#endif
Q_DECLARE_PUBLIC(QBluetoothServiceDiscoveryAgent)
public:
@@ -186,6 +196,17 @@ private:
QMap<QBluetoothAddress,QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > > sdpCache;
#endif
+#ifdef QT_WINRT_BLUETOOTH
+private slots:
+ void processFoundService(quint64 deviceAddress, const QBluetoothServiceInfo &info);
+ void onScanFinished(quint64 deviceAddress);
+ void onScanCanceled();
+ void onError();
+
+private:
+ QPointer<QWinRTBluetoothServiceDiscoveryWorker> worker;
+#endif
+
protected:
QBluetoothServiceDiscoveryAgent *q_ptr;
};
diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp
new file mode 100644
index 00000000..1d98874f
--- /dev/null
+++ b/src/bluetooth/qbluetoothservicediscoveryagent_winrt.cpp
@@ -0,0 +1,604 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothservicediscoveryagent.h"
+#include "qbluetoothservicediscoveryagent_p.h"
+
+#include <qfunctions_winrt.h>
+#include <QtCore/QLoggingCategory>
+#include <QtCore/private/qeventdispatcher_winrt_p.h>
+
+#include <functional>
+#include <robuffer.h>
+#include <windows.devices.enumeration.h>
+#include <windows.devices.bluetooth.h>
+#include <windows.foundation.collections.h>
+#include <windows.storage.streams.h>
+#include <wrl.h>
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Foundation::Collections;
+using namespace ABI::Windows::Devices;
+using namespace ABI::Windows::Devices::Bluetooth;
+using namespace ABI::Windows::Devices::Bluetooth::Rfcomm;
+using namespace ABI::Windows::Devices::Enumeration;
+using namespace ABI::Windows::Storage::Streams;
+
+typedef Collections::IKeyValuePair<UINT32, IBuffer *> ValueItem;
+typedef Collections::IIterable<ValueItem *> ValueIterable;
+typedef Collections::IIterator<ValueItem *> ValueIterator;
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT)
+
+#define TYPE_UINT8 8
+#define TYPE_UINT16 9
+#define TYPE_UINT32 10
+#define TYPE_SHORT_UUID 25
+#define TYPE_LONG_UUID 28
+#define TYPE_STRING 37
+#define TYPE_SEQUENCE 53
+
+static QByteArray byteArrayFromBuffer(const ComPtr<IBuffer> &buffer, bool isWCharString = false)
+{
+ ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteAccess;
+ HRESULT hr = buffer.As(&byteAccess);
+ Q_ASSERT_SUCCEEDED(hr);
+ char *data;
+ hr = byteAccess->Buffer(reinterpret_cast<byte **>(&data));
+ Q_ASSERT_SUCCEEDED(hr);
+ UINT32 size;
+ hr = buffer->get_Length(&size);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (isWCharString) {
+ QString valueString = QString::fromUtf16(reinterpret_cast<ushort *>(data)).left(size / 2);
+ return valueString.toUtf8();
+ }
+ return QByteArray(data, size);
+}
+
+class QWinRTBluetoothServiceDiscoveryWorker : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QWinRTBluetoothServiceDiscoveryWorker(quint64 targetAddress,
+ QBluetoothServiceDiscoveryAgent::DiscoveryMode mode);
+ ~QWinRTBluetoothServiceDiscoveryWorker();
+ void start();
+
+Q_SIGNALS:
+ void serviceFound(quint64 deviceAddress, const QBluetoothServiceInfo &info);
+ void scanFinished(quint64 deviceAddress);
+ void scanCanceled();
+ void errorOccured();
+
+private:
+ HRESULT onBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status);
+
+ void processServiceSearchResult(quint64 address, ComPtr<IVectorView<RfcommDeviceService*>> services);
+ QBluetoothServiceInfo::Sequence readSequence(ComPtr<IDataReader> dataReader, bool *ok, quint8 *bytesRead);
+
+private:
+ quint64 m_targetAddress;
+ QBluetoothServiceDiscoveryAgent::DiscoveryMode m_mode;
+};
+
+QWinRTBluetoothServiceDiscoveryWorker::QWinRTBluetoothServiceDiscoveryWorker(quint64 targetAddress,
+ QBluetoothServiceDiscoveryAgent::DiscoveryMode mode)
+ : m_targetAddress(targetAddress)
+ , m_mode(mode)
+{
+}
+
+QWinRTBluetoothServiceDiscoveryWorker::~QWinRTBluetoothServiceDiscoveryWorker()
+{
+}
+
+void QWinRTBluetoothServiceDiscoveryWorker::start()
+{
+ HRESULT hr;
+ hr = QEventDispatcherWinRT::runOnXamlThread([this]() {
+ ComPtr<IBluetoothDeviceStatics> deviceStatics;
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Bluetooth_BluetoothDevice).Get(), &deviceStatics);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<BluetoothDevice *>> deviceFromAddressOperation;
+ hr = deviceStatics->FromBluetoothAddressAsync(m_targetAddress, &deviceFromAddressOperation);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = deviceFromAddressOperation->put_Completed(Callback<IAsyncOperationCompletedHandler<BluetoothDevice *>>
+ (this, &QWinRTBluetoothServiceDiscoveryWorker::onBluetoothDeviceFoundAsync).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+}
+
+HRESULT QWinRTBluetoothServiceDiscoveryWorker::onBluetoothDeviceFoundAsync(IAsyncOperation<BluetoothDevice *> *op, AsyncStatus status)
+{
+ if (status != Completed) {
+ qCDebug(QT_BT_WINRT) << "Could not find device";
+ emit errorOccured();
+ return S_OK;
+ }
+
+ ComPtr<IBluetoothDevice> device;
+ HRESULT hr;
+ hr = op->GetResults(&device);
+ Q_ASSERT_SUCCEEDED(hr);
+ quint64 address;
+ device->get_BluetoothAddress(&address);
+
+#ifdef QT_WINRT_LIMITED_SERVICEDISCOVERY
+ if (m_mode != QBluetoothServiceDiscoveryAgent::MinimalDiscovery) {
+ qWarning() << "Used Windows SDK version (" << QString::number(QT_UCRTVERSION) << ") does not "
+ "support full service discovery. Consider updating to a more recent Windows 10 "
+ "SDK (14393 or above).";
+ }
+ ComPtr<IVectorView<RfcommDeviceService*>> commServices;
+ hr = device->get_RfcommServices(&commServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ processServiceSearchResult(address, commServices);
+#else // !QT_WINRT_LIMITED_SERVICEDISOVERY
+ ComPtr<IBluetoothDevice3> device3;
+ hr = device.As(&device3);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IAsyncOperation<RfcommDeviceServicesResult *>> serviceOp;
+ const BluetoothCacheMode cacheMode = m_mode == QBluetoothServiceDiscoveryAgent::MinimalDiscovery
+ ? BluetoothCacheMode_Cached : BluetoothCacheMode_Uncached;
+ hr = device3->GetRfcommServicesWithCacheModeAsync(cacheMode, &serviceOp);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = serviceOp->put_Completed(Callback<IAsyncOperationCompletedHandler<RfcommDeviceServicesResult *>>
+ ([address, this](IAsyncOperation<RfcommDeviceServicesResult *> *op, AsyncStatus status)
+ {
+ if (status != Completed) {
+ qCDebug(QT_BT_WINRT) << "Could not obtain service list";
+ emit errorOccured();
+ return S_OK;
+ }
+
+ ComPtr<IRfcommDeviceServicesResult> result;
+ HRESULT hr = op->GetResults(&result);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IVectorView<RfcommDeviceService*>> commServices;
+ hr = result->get_Services(&commServices);
+ Q_ASSERT_SUCCEEDED(hr);
+ processServiceSearchResult(address, commServices);
+ return S_OK;
+ }).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+#endif // !QT_WINRT_LIMITED_SERVICEDISOVERY
+
+ return S_OK;
+}
+
+void QWinRTBluetoothServiceDiscoveryWorker::processServiceSearchResult(quint64 address, ComPtr<IVectorView<RfcommDeviceService*>> services)
+{
+ quint32 size;
+ HRESULT hr;
+ hr = services->get_Size(&size);
+ Q_ASSERT_SUCCEEDED(hr);
+ for (quint32 i = 0; i < size; ++i) {
+ ComPtr<IRfcommDeviceService> service;
+ hr = services->GetAt(i, &service);
+ Q_ASSERT_SUCCEEDED(hr);
+ HString name;
+ hr = service->get_ConnectionServiceName(name.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ const QString serviceName = QString::fromWCharArray(WindowsGetStringRawBuffer(name.Get(), nullptr));
+ ComPtr<IRfcommServiceId> id;
+ hr = service->get_ServiceId(&id);
+ Q_ASSERT_SUCCEEDED(hr);
+ GUID guid;
+ hr = id->get_Uuid(&guid);
+ const QBluetoothUuid uuid(guid);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ QBluetoothServiceInfo info;
+ info.setServiceName(serviceName);
+ info.setServiceUuid(uuid);
+ ComPtr<IAsyncOperation<IMapView<UINT32, IBuffer *> *>> op;
+ hr = service->GetSdpRawAttributesAsync(op.GetAddressOf());
+ if (FAILED(hr)) {
+ emit errorOccured();
+ qDebug() << "Check manifest capabilities";
+ continue;
+ }
+ ComPtr<IMapView<UINT32, IBuffer *>> mapView;
+ hr = QWinRTFunctions::await(op, mapView.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ // TODO timeout
+ ComPtr<ValueIterable> iterable;
+ ComPtr<ValueIterator> iterator;
+
+ hr = mapView.As(&iterable);
+ if (FAILED(hr))
+ continue;
+
+ boolean current = false;
+ hr = iterable->First(&iterator);
+ if (FAILED(hr))
+ continue;
+ hr = iterator->get_HasCurrent(&current);
+ if (FAILED(hr))
+ continue;
+
+ while (SUCCEEDED(hr) && current) {
+ ComPtr<ValueItem> item;
+ hr = iterator->get_Current(&item);
+ if (FAILED(hr))
+ continue;
+
+ UINT32 key;
+ hr = item->get_Key(&key);
+ if (FAILED(hr))
+ continue;
+
+ ComPtr<IBuffer> buffer;
+ hr = item->get_Value(&buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ ComPtr<IDataReader> dataReader;
+ ComPtr<IDataReaderStatics> dataReaderStatics;
+ hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataReader).Get(), &dataReaderStatics);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = dataReaderStatics->FromBuffer(buffer.Get(), dataReader.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ BYTE type;
+ hr = dataReader->ReadByte(&type);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (type == TYPE_UINT8) {
+ quint8 value;
+ hr = dataReader->ReadByte(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ info.setAttribute(key, value);
+ qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "UINT8" << hex << value;
+ } else if (type == TYPE_UINT16) {
+ quint16 value;
+ hr = dataReader->ReadUInt16(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ info.setAttribute(key, value);
+ qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "UINT16" << hex << value;
+ } else if (type == TYPE_UINT32) {
+ quint32 value;
+ hr = dataReader->ReadUInt32(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ info.setAttribute(key, value);
+ qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "UINT32" << hex << value;
+ } else if (type == TYPE_SHORT_UUID) {
+ quint16 value;
+ hr = dataReader->ReadUInt16(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ const QBluetoothUuid uuid(value);
+ info.setAttribute(key, uuid);
+ qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "UUID" << hex << uuid;
+ } else if (type == TYPE_LONG_UUID) {
+ GUID value;
+ hr = dataReader->ReadGuid(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ const QBluetoothUuid uuid(value);
+ info.setAttribute(key, uuid);
+ qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "UUID" << hex << uuid;
+ } else if (type == TYPE_STRING) {
+ BYTE length;
+ hr = dataReader->ReadByte(&length);
+ Q_ASSERT_SUCCEEDED(hr);
+ HString value;
+ hr = dataReader->ReadString(length, value.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ const QString str = QString::fromWCharArray(WindowsGetStringRawBuffer(value.Get(), nullptr));
+ info.setAttribute(key, str);
+ qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "STRING" << str;
+ } else if (type == TYPE_SEQUENCE) {
+ bool ok;
+ QBluetoothServiceInfo::Sequence sequence = readSequence(dataReader, &ok, nullptr);
+ if (ok) {
+ info.setAttribute(key, sequence);
+ qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "SEQUENCE" << sequence;
+ } else {
+ qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type << "SEQUENCE ERROR";
+ }
+ } else {
+ qCDebug(QT_BT_WINRT) << "UUID" << uuid << "KEY" << hex << key << "TYPE" << dec << type;
+ }
+ hr = iterator->MoveNext(&current);
+ }
+ emit serviceFound(address, info);
+ }
+ emit scanFinished(address);
+ deleteLater();
+}
+
+QBluetoothServiceInfo::Sequence QWinRTBluetoothServiceDiscoveryWorker::readSequence(ComPtr<IDataReader> dataReader, bool *ok, quint8 *bytesRead)
+{
+ if (ok)
+ *ok = false;
+ if (bytesRead)
+ *bytesRead = 0;
+ QBluetoothServiceInfo::Sequence result;
+ if (!dataReader)
+ return result;
+
+ quint8 remainingLength;
+ HRESULT hr = dataReader->ReadByte(&remainingLength);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (bytesRead)
+ *bytesRead += 1;
+ BYTE type;
+ hr = dataReader->ReadByte(&type);
+ remainingLength -= 1;
+ if (bytesRead)
+ *bytesRead += 1;
+ Q_ASSERT_SUCCEEDED(hr);
+ while (true) {
+ switch (type) {
+ case TYPE_UINT8: {
+ quint8 value;
+ hr = dataReader->ReadByte(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ result.append(QVariant::fromValue(value));
+ remainingLength -= 1;
+ if (bytesRead)
+ *bytesRead += 1;
+ break;
+ }
+ case TYPE_UINT16: {
+ quint16 value;
+ hr = dataReader->ReadUInt16(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ result.append(QVariant::fromValue(value));
+ remainingLength -= 2;
+ if (bytesRead)
+ *bytesRead += 2;
+ break;
+ }
+ case TYPE_UINT32: {
+ quint32 value;
+ hr = dataReader->ReadUInt32(&value);
+ Q_ASSERT_SUCCEEDED(hr);
+ result.append(QVariant::fromValue(value));
+ remainingLength -= 4;
+ if (bytesRead)
+ *bytesRead += 4;
+ break;
+ }
+ case TYPE_SHORT_UUID: {
+ quint16 b;
+ hr = dataReader->ReadUInt16(&b);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ const QBluetoothUuid uuid(b);
+ result.append(QVariant::fromValue(uuid));
+ remainingLength -= 2;
+ if (bytesRead)
+ *bytesRead += 2;
+ break;
+ }
+ case TYPE_LONG_UUID: {
+ GUID b;
+ hr = dataReader->ReadGuid(&b);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ const QBluetoothUuid uuid(b);
+ result.append(QVariant::fromValue(uuid));
+ remainingLength -= sizeof(GUID);
+ if (bytesRead)
+ *bytesRead += sizeof(GUID);
+ break;
+ }
+ case TYPE_STRING: {
+ BYTE length;
+ hr = dataReader->ReadByte(&length);
+ Q_ASSERT_SUCCEEDED(hr);
+ HString value;
+ hr = dataReader->ReadString(length, value.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+
+ const QString str = QString::fromWCharArray(WindowsGetStringRawBuffer(value.Get(), nullptr));
+ result.append(QVariant::fromValue(str));
+ remainingLength -= length;
+ if (bytesRead)
+ *bytesRead += 2;
+ break;
+ }
+ case TYPE_SEQUENCE: {
+ quint8 bytesR;
+ const QBluetoothServiceInfo::Sequence sequence = readSequence(dataReader, ok, &bytesR);
+ if (*ok)
+ result.append(QVariant::fromValue(sequence));
+ else
+ return result;
+ remainingLength -= bytesR;
+ if (bytesRead)
+ *bytesRead += bytesR;
+ break;
+ }
+ default:
+ qCDebug(QT_BT_WINRT) << "SEQUENCE ERROR" << type;
+ result.clear();
+ return result;
+ }
+ if (remainingLength == 0)
+ break;
+
+ hr = dataReader->ReadByte(&type);
+ Q_ASSERT_SUCCEEDED(hr);
+ remainingLength -= 1;
+ if (bytesRead)
+ *bytesRead += 1;
+ }
+
+ if (ok)
+ *ok = true;
+ return result;
+}
+
+QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(
+ QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter)
+ : error(QBluetoothServiceDiscoveryAgent::NoError),
+ state(Inactive),
+ deviceDiscoveryAgent(0),
+ mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery),
+ singleDevice(false),
+ q_ptr(qp)
+{
+ // TODO: use local adapter for discovery. Possible?
+ Q_UNUSED(deviceAdapter);
+}
+
+QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate()
+{
+ stop();
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address)
+{
+ if (worker)
+ return;
+
+ worker = new QWinRTBluetoothServiceDiscoveryWorker(address.toUInt64(), mode);
+
+ connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::serviceFound,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService, Qt::QueuedConnection);
+ connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished, Qt::QueuedConnection);
+ connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanCanceled,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::onScanCanceled, Qt::QueuedConnection);
+ connect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::onError, Qt::QueuedConnection);
+ worker->start();
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::stop()
+{
+ if (!worker)
+ return;
+
+ disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::serviceFound,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::processFoundService);
+ disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanFinished,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::onScanFinished);
+ disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::scanCanceled,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::onScanCanceled);
+ disconnect(worker, &QWinRTBluetoothServiceDiscoveryWorker::errorOccured,
+ this, &QBluetoothServiceDiscoveryAgentPrivate::onError);
+ // mWorker will delete itself as soon as it is done with its discovery
+ worker = nullptr;
+ setDiscoveryState(Inactive);
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::processFoundService(quint64 deviceAddress, const QBluetoothServiceInfo &info)
+{
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+ //apply uuidFilter
+ if (!uuidFilter.isEmpty()) {
+ bool serviceNameMatched = uuidFilter.contains(info.serviceUuid());
+ bool serviceClassMatched = false;
+ for (const QBluetoothUuid &id : info.serviceClassUuids()) {
+ if (uuidFilter.contains(id)) {
+ serviceClassMatched = true;
+ break;
+ }
+ }
+
+ if (!serviceNameMatched && !serviceClassMatched)
+ return;
+ }
+
+ if (!info.isValid())
+ return;
+
+ QBluetoothServiceInfo returnInfo(info);
+ bool deviceFound;
+ for (const QBluetoothDeviceInfo &deviceInfo : discoveredDevices) {
+ if (deviceInfo.address().toUInt64() == deviceAddress) {
+ deviceFound = true;
+ returnInfo.setDevice(deviceInfo);
+ break;
+ }
+ }
+ Q_ASSERT(deviceFound);
+
+ if (!isDuplicatedService(returnInfo)) {
+ discoveredServices.append(returnInfo);
+ qCDebug(QT_BT_WINRT) << "Discovered services" << discoveredDevices.at(0).address().toString()
+ << returnInfo.serviceName() << returnInfo.serviceUuid()
+ << ">>>" << returnInfo.serviceClassUuids();
+
+ emit q->serviceDiscovered(returnInfo);
+ }
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::onScanFinished(quint64 deviceAddress)
+{
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+ bool deviceFound;
+ for (const QBluetoothDeviceInfo &deviceInfo : discoveredDevices) {
+ if (deviceInfo.address().toUInt64() == deviceAddress) {
+ deviceFound = true;
+ discoveredDevices.removeOne(deviceInfo);
+ if (discoveredDevices.isEmpty())
+ setDiscoveryState(Inactive);
+ break;
+ }
+ }
+ Q_ASSERT(deviceFound);
+ stop();
+ emit q->finished();
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::onScanCanceled()
+{
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+ emit q->canceled();
+}
+
+void QBluetoothServiceDiscoveryAgentPrivate::onError()
+{
+ Q_Q(QBluetoothServiceDiscoveryAgent);
+ discoveredDevices.clear();
+ error = QBluetoothServiceDiscoveryAgent::InputOutputError;
+ errorString = "errorDescription";
+ emit q->error(error);
+}
+
+QT_END_NAMESPACE
+
+#include <qbluetoothservicediscoveryagent_winrt.moc>
diff --git a/src/bluetooth/qbluetoothsocket.cpp b/src/bluetooth/qbluetoothsocket.cpp
index d396ca84..3e961142 100644
--- a/src/bluetooth/qbluetoothsocket.cpp
+++ b/src/bluetooth/qbluetoothsocket.cpp
@@ -338,7 +338,14 @@ void QBluetoothSocket::connectToService(const QBluetoothServiceInfo &service, Op
}
d->connectToService(service.device().address(), service.serviceUuid(), openMode);
#else
- // Report this problem early:
+#if defined(QT_WINRT_BLUETOOTH)
+ // Report these problems early:
+ if (socketType() != QBluetoothServiceInfo::RfcommProtocol) {
+ d->errorString = tr("Socket type not supported");
+ setSocketError(QBluetoothSocket::UnsupportedProtocolError);
+ return;
+ }
+#endif // QT_WINRT_BLUETOOTH
if (socketType() == QBluetoothServiceInfo::UnknownProtocol) {
qCWarning(QT_BT) << "QBluetoothSocket::connectToService cannot "
"connect with 'UnknownProtocol' type";
@@ -416,7 +423,14 @@ void QBluetoothSocket::connectToService(const QBluetoothAddress &address, const
}
d->connectToService(address, uuid, openMode);
#else
- // Report this problem early, prevent device discovery:
+#if defined(QT_WINRT_BLUETOOTH)
+ // Report these problems early, prevent device discovery:
+ if (socketType() != QBluetoothServiceInfo::RfcommProtocol) {
+ d->errorString = tr("Socket type not supported");
+ setSocketError(QBluetoothSocket::UnsupportedProtocolError);
+ return;
+ }
+#endif // QT_WINRT_BLUETOOTH
if (socketType() == QBluetoothServiceInfo::UnknownProtocol) {
qCWarning(QT_BT) << "QBluetoothSocket::connectToService cannot "
"connect with 'UnknownProtocol' type";
@@ -462,7 +476,14 @@ void QBluetoothSocket::connectToService(const QBluetoothAddress &address, quint1
setSocketError(QBluetoothSocket::ServiceNotFoundError);
qCWarning(QT_BT) << "Connecting to port is not supported";
#else
- // Report this problem early:
+#if defined(QT_WINRT_BLUETOOTH)
+ // Report these problems early
+ if (socketType() != QBluetoothServiceInfo::RfcommProtocol) {
+ d->errorString = tr("Socket type not supported");
+ setSocketError(QBluetoothSocket::UnsupportedProtocolError);
+ return;
+ }
+#endif // QT_WINRT_BLUETOOTH
if (socketType() == QBluetoothServiceInfo::UnknownProtocol) {
qCWarning(QT_BT) << "QBluetoothSocket::connectToService cannot "
"connect with 'UnknownProtocol' type";
diff --git a/src/bluetooth/qbluetoothsocket_p.h b/src/bluetooth/qbluetoothsocket_p.h
index 147427fd..991a60d7 100644
--- a/src/bluetooth/qbluetoothsocket_p.h
+++ b/src/bluetooth/qbluetoothsocket_p.h
@@ -61,6 +61,26 @@
class WorkerThread;
#endif
+#ifdef QT_WINRT_BLUETOOTH
+#include <wrl.h>
+
+namespace ABI {
+ namespace Windows {
+ namespace Networking {
+ namespace Sockets {
+ struct IStreamSocket;
+ }
+ }
+ namespace Foundation {
+ struct IAsyncAction;
+ enum class AsyncStatus;
+ }
+ }
+}
+
+class SocketWorker;
+#endif // QT_WINRT_BLUETOOTH
+
#ifndef QPRIVATELINEARBUFFER_BUFFERSIZE
#define QPRIVATELINEARBUFFER_BUFFERSIZE Q_INT64_C(16384)
#endif
@@ -183,6 +203,24 @@ signals:
#endif
+#ifdef QT_WINRT_BLUETOOTH
+ SocketWorker *m_worker;
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Networking::Sockets::IStreamSocket> m_socketObject;
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> m_connectOp;
+
+ QMutex m_readMutex;
+
+ // Protected by m_readMutex. Written in addToPendingData (native callback)
+ QVector<QByteArray> m_pendingData;
+
+ Q_INVOKABLE void addToPendingData(const QVector<QByteArray> &data);
+
+private slots:
+ void handleNewData(const QVector<QByteArray> &data);
+ void handleError(QBluetoothSocket::SocketError error);
+#endif // QT_WINRT_BLUETOOTH
+
#if defined(QT_BLUEZ_BLUETOOTH)
private slots:
void _q_readNotify();
@@ -194,6 +232,10 @@ protected:
private:
+#ifdef QT_WINRT_BLUETOOTH
+ HRESULT handleConnectOpFinished(ABI::Windows::Foundation::IAsyncAction *action, ABI::Windows::Foundation::AsyncStatus status);
+#endif
+
#ifdef QT_BLUEZ_BLUETOOTH
public:
quint8 lowEnergySocketType;
diff --git a/src/bluetooth/qbluetoothsocket_winrt.cpp b/src/bluetooth/qbluetoothsocket_winrt.cpp
new file mode 100644
index 00000000..b8a57adb
--- /dev/null
+++ b/src/bluetooth/qbluetoothsocket_winrt.cpp
@@ -0,0 +1,644 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtBluetooth module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbluetoothsocket.h"
+#include "qbluetoothsocket_p.h"
+
+#include <qfunctions_winrt.h>
+
+#include <private/qeventdispatcher_winrt_p.h>
+
+#include <QtBluetooth/QBluetoothLocalDevice>
+#include <QtCore/qloggingcategory.h>
+
+#include <robuffer.h>
+#include <windows.devices.bluetooth.h>
+#include <windows.networking.sockets.h>
+#include <windows.storage.streams.h>
+#include <wrl.h>
+
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Devices::Bluetooth;
+using namespace ABI::Windows::Devices::Bluetooth::Rfcomm;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Foundation::Collections;
+using namespace ABI::Windows::Networking;
+using namespace ABI::Windows::Networking::Sockets;
+using namespace ABI::Windows::Storage::Streams;
+
+typedef IAsyncOperationWithProgressCompletedHandler<IBuffer *, UINT32> SocketReadCompletedHandler;
+typedef IAsyncOperationWithProgress<IBuffer *, UINT32> IAsyncBufferOperation;
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT)
+
+struct SocketGlobal
+{
+ SocketGlobal()
+ {
+ HRESULT hr;
+ hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(),
+ &bufferFactory);
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+
+ ComPtr<IBufferFactory> bufferFactory;
+};
+Q_GLOBAL_STATIC(SocketGlobal, g)
+
+#define READ_BUFFER_SIZE 65536
+
+static inline QString qt_QStringFromHString(const HString &string)
+{
+ UINT32 length;
+ PCWSTR rawString = string.GetRawBuffer(&length);
+ return QString::fromWCharArray(rawString, length);
+}
+
+static qint64 writeIOStream(ComPtr<IOutputStream> stream, const char *data, qint64 len)
+{
+ ComPtr<IBuffer> buffer;
+ HRESULT hr = g->bufferFactory->Create(len, &buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = buffer->put_Length(len);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteArrayAccess;
+ hr = buffer.As(&byteArrayAccess);
+ Q_ASSERT_SUCCEEDED(hr);
+ byte *bytes;
+ hr = byteArrayAccess->Buffer(&bytes);
+ Q_ASSERT_SUCCEEDED(hr);
+ memcpy(bytes, data, len);
+ ComPtr<IAsyncOperationWithProgress<UINT32, UINT32>> op;
+ hr = stream->WriteAsync(buffer.Get(), &op);
+ RETURN_IF_FAILED("Failed to write to stream", return -1);
+ UINT32 bytesWritten;
+ hr = QWinRTFunctions::await(op, &bytesWritten);
+ RETURN_IF_FAILED("Failed to write to stream", return -1);
+ return bytesWritten;
+}
+
+class SocketWorker : public QObject
+{
+ Q_OBJECT
+public:
+ SocketWorker()
+ {
+ }
+
+ ~SocketWorker()
+ {
+ if (Q_UNLIKELY(m_initialReadOp)) {
+ ComPtr<IAsyncInfo> info;
+ HRESULT hr = m_initialReadOp.As(&info);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (info) {
+ hr = info->Cancel();
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = info->Close();
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ }
+
+ if (m_readOp) {
+ ComPtr<IAsyncInfo> info;
+ HRESULT hr = m_readOp.As(&info);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (info) {
+ hr = info->Cancel();
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = info->Close();
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ }
+ }
+
+signals:
+ void newDataReceived(const QVector<QByteArray> &data);
+ void socketErrorOccured(QBluetoothSocket::SocketError error);
+
+public slots:
+ Q_INVOKABLE void notifyAboutNewData()
+ {
+ QMutexLocker locker(&m_mutex);
+ const QVector<QByteArray> newData = std::move(m_pendingData);
+ m_pendingData.clear();
+ emit newDataReceived(newData);
+ }
+
+public:
+ void startReading()
+ {
+ ComPtr<IBuffer> buffer;
+ HRESULT hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IInputStream> stream;
+ hr = m_socket->get_InputStream(&stream);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, m_initialReadOp.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = m_initialReadOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketWorker::onReadyRead).Get());
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+
+ HRESULT onReadyRead(IAsyncBufferOperation *asyncInfo, AsyncStatus status)
+ {
+ if (asyncInfo == m_initialReadOp.Get()) {
+ m_initialReadOp.Reset();
+ } else if (asyncInfo == m_readOp.Get()) {
+ m_readOp.Reset();
+ } else {
+ Q_ASSERT(false);
+ }
+
+ // A read in UnconnectedState will close the socket and return -1 and thus tell the caller,
+ // that the connection was closed. The socket cannot be closed here, as the subsequent read
+ // might fail then.
+ if (status == Error || status == Canceled) {
+ emit socketErrorOccured(QBluetoothSocket::NetworkError);
+ return S_OK;
+ }
+
+ ComPtr<IBuffer> buffer;
+ HRESULT hr = asyncInfo->GetResults(&buffer);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "Failed to get read results buffer");
+ emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
+ return S_OK;
+ }
+
+ UINT32 bufferLength;
+ hr = buffer->get_Length(&bufferLength);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "Failed to get buffer length");
+ emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
+ return S_OK;
+ }
+ // A zero sized buffer length signals, that the remote host closed the connection. The socket
+ // cannot be closed though, as the following read might have socket descriptor -1 and thus and
+ // the closing of the socket won't be communicated to the caller. So only the error is set. The
+ // actual socket close happens inside of read.
+ if (!bufferLength) {
+ emit socketErrorOccured(QBluetoothSocket::NetworkError);
+ return S_OK;
+ }
+
+ ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteArrayAccess;
+ hr = buffer.As(&byteArrayAccess);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "Failed to get cast buffer");
+ emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
+ return S_OK;
+ }
+ byte *data;
+ hr = byteArrayAccess->Buffer(&data);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "Failed to access buffer data");
+ emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
+ return S_OK;
+ }
+
+ QByteArray newData(reinterpret_cast<const char*>(data), qint64(bufferLength));
+ QMutexLocker readLocker(&m_mutex);
+ if (m_pendingData.isEmpty())
+ QMetaObject::invokeMethod(this, "notifyAboutNewData", Qt::QueuedConnection);
+ m_pendingData << newData;
+ readLocker.unlock();
+
+ hr = QEventDispatcherWinRT::runOnXamlThread([buffer, this]() {
+ UINT32 readBufferLength;
+ ComPtr<IInputStream> stream;
+ HRESULT hr = m_socket->get_InputStream(&stream);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "Failed to obtain input stream");
+ emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
+ return S_OK;
+ }
+
+ // Reuse the stream buffer
+ hr = buffer->get_Capacity(&readBufferLength);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "Failed to get buffer capacity");
+ emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
+ return S_OK;
+ }
+ hr = buffer->put_Length(0);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "Failed to set buffer length");
+ emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
+ return S_OK;
+ }
+
+ hr = stream->ReadAsync(buffer.Get(), readBufferLength, InputStreamOptions_Partial, &m_readOp);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "onReadyRead(): Could not read into socket stream buffer.");
+ emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
+ return S_OK;
+ }
+ hr = m_readOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketWorker::onReadyRead).Get());
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "onReadyRead(): Failed to set socket read callback.");
+ emit socketErrorOccured(QBluetoothSocket::UnknownSocketError);
+ return S_OK;
+ }
+ return S_OK;
+ });
+ Q_ASSERT_SUCCEEDED(hr);
+ return S_OK;
+ }
+
+ void setSocket(ComPtr<IStreamSocket> socket) { m_socket = socket; }
+
+private:
+ ComPtr<IStreamSocket> m_socket;
+ QVector<QByteArray> m_pendingData;
+
+ // Protects pendingData/pendingDatagrams which are accessed from native callbacks
+ QMutex m_mutex;
+
+ ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> m_initialReadOp;
+ ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> m_readOp;
+};
+
+QBluetoothSocketPrivate::QBluetoothSocketPrivate()
+ : socket(-1),
+ socketType(QBluetoothServiceInfo::UnknownProtocol),
+ state(QBluetoothSocket::UnconnectedState),
+ socketError(QBluetoothSocket::NoSocketError),
+ secFlags(QBluetooth::NoSecurity),
+ m_worker(new SocketWorker())
+{
+ connect(m_worker, &SocketWorker::newDataReceived,
+ this, &QBluetoothSocketPrivate::handleNewData, Qt::QueuedConnection);
+ connect(m_worker, &SocketWorker::socketErrorOccured,
+ this, &QBluetoothSocketPrivate::handleError, Qt::QueuedConnection);
+}
+
+QBluetoothSocketPrivate::~QBluetoothSocketPrivate()
+{
+ abort();
+}
+
+bool QBluetoothSocketPrivate::ensureNativeSocket(QBluetoothServiceInfo::Protocol type)
+{
+ if (socket != -1) {
+ if (type == socketType)
+ return true;
+ m_socketObject = nullptr;
+ socket = -1;
+ }
+ socketType = type;
+ if (socketType != QBluetoothServiceInfo::RfcommProtocol)
+ return false;
+
+ HRESULT hr;
+ hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_StreamSocket).Get(), &m_socketObject);
+ if (FAILED(hr) || !m_socketObject) {
+ qErrnoWarning(hr, "ensureNativeSocket: Could not create socket instance");
+ return false;
+ }
+ socket = qintptr(m_socketObject.Get());
+ m_worker->setSocket(m_socketObject);
+
+ return true;
+}
+
+void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode)
+{
+ Q_Q(QBluetoothSocket);
+ Q_UNUSED(openMode);
+
+ if (socket == -1 && !ensureNativeSocket(socketType)) {
+ errorString = QBluetoothSocket::tr("Unknown socket error");
+ q->setSocketError(QBluetoothSocket::UnknownSocketError);
+ return;
+ }
+
+ const QString addressString = address.toString();
+ HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(addressString.utf16()));
+ ComPtr<IHostNameFactory> hostNameFactory;
+ HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(),
+ &hostNameFactory);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IHostName> remoteHost;
+ hr = hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost);
+ RETURN_VOID_IF_FAILED("QBluetoothSocketPrivate::connectToService: Could not create hostname.");
+
+ const QString portString = QString::number(port);
+ HStringReference portReference(reinterpret_cast<LPCWSTR>(portString.utf16()));
+
+ hr = m_socketObject->ConnectAsync(remoteHost.Get(), portReference.Get(), &m_connectOp);
+ if (hr == E_ACCESSDENIED) {
+ qErrnoWarning(hr, "QBluetoothSocketPrivate::connectToService: Unable to connect to bluetooth socket."
+ "Please check your manifest capabilities.");
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+ return;
+ }
+ Q_ASSERT_SUCCEEDED(hr);
+
+ q->setSocketState(QBluetoothSocket::ConnectingState);
+ q->setOpenMode(openMode);
+ QEventDispatcherWinRT::runOnXamlThread([this]() {
+ HRESULT hr;
+ hr = m_connectOp->put_Completed(Callback<IAsyncActionCompletedHandler>(
+ this, &QBluetoothSocketPrivate::handleConnectOpFinished).Get());
+ RETURN_HR_IF_FAILED("connectToHostByName: Could not register \"connectOp\" callback");
+ return S_OK;
+ });
+}
+
+void QBluetoothSocketPrivate::abort()
+{
+ Q_Q(QBluetoothSocket);
+ if (state == QBluetoothSocket::UnconnectedState)
+ return;
+
+ disconnect(m_worker, &SocketWorker::newDataReceived,
+ this, &QBluetoothSocketPrivate::handleNewData);
+ disconnect(m_worker, &SocketWorker::socketErrorOccured,
+ this, &QBluetoothSocketPrivate::handleError);
+ m_worker->deleteLater();
+
+ if (socket != -1) {
+ m_socketObject = nullptr;
+ socket = -1;
+ }
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+}
+
+QString QBluetoothSocketPrivate::localName() const
+{
+ const QBluetoothAddress address = localAddress();
+ if (address.isNull())
+ return QString();
+
+ QBluetoothLocalDevice device(address);
+ return device.name();
+}
+
+QBluetoothAddress QBluetoothSocketPrivate::localAddress() const
+{
+ HRESULT hr;
+ ComPtr<IStreamSocketInformation> info;
+ hr = m_socketObject->get_Information(&info);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IHostName> localHost;
+ hr = info->get_LocalAddress(&localHost);
+ Q_ASSERT_SUCCEEDED(hr);
+ HString localAddress;
+ hr = localHost->get_CanonicalName(localAddress.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ return QBluetoothAddress(qt_QStringFromHString(localAddress));
+}
+
+quint16 QBluetoothSocketPrivate::localPort() const
+{
+ HRESULT hr;
+ ComPtr<IStreamSocketInformation> info;
+ hr = m_socketObject->get_Information(&info);
+ Q_ASSERT_SUCCEEDED(hr);
+ HString localPortString;
+ hr = info->get_LocalPort(localPortString.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ return qt_QStringFromHString(localPortString).toInt();
+}
+
+QString QBluetoothSocketPrivate::peerName() const
+{
+ HRESULT hr;
+ ComPtr<IStreamSocketInformation> info;
+ hr = m_socketObject->get_Information(&info);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IHostName> remoteHost;
+ hr = info->get_RemoteHostName(&remoteHost);
+ Q_ASSERT_SUCCEEDED(hr);
+ HString remoteHostName;
+ hr = remoteHost->get_DisplayName(remoteHostName.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ return qt_QStringFromHString(remoteHostName);
+}
+
+QBluetoothAddress QBluetoothSocketPrivate::peerAddress() const
+{
+ HRESULT hr;
+ ComPtr<IStreamSocketInformation> info;
+ hr = m_socketObject->get_Information(&info);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IHostName> remoteHost;
+ hr = info->get_RemoteAddress(&remoteHost);
+ Q_ASSERT_SUCCEEDED(hr);
+ HString remoteAddress;
+ hr = remoteHost->get_CanonicalName(remoteAddress.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ return QBluetoothAddress(qt_QStringFromHString(remoteAddress));
+}
+
+quint16 QBluetoothSocketPrivate::peerPort() const
+{
+ HRESULT hr;
+ ComPtr<IStreamSocketInformation> info;
+ hr = m_socketObject->get_Information(&info);
+ Q_ASSERT_SUCCEEDED(hr);
+ HString remotePortString;
+ hr = info->get_LocalPort(remotePortString.GetAddressOf());
+ Q_ASSERT_SUCCEEDED(hr);
+ return qt_QStringFromHString(remotePortString).toInt();
+}
+
+qint64 QBluetoothSocketPrivate::writeData(const char *data, qint64 maxSize)
+{
+ Q_Q(QBluetoothSocket);
+
+ if (state != QBluetoothSocket::ConnectedState) {
+ errorString = QBluetoothSocket::tr("Cannot write while not connected");
+ q->setSocketError(QBluetoothSocket::OperationError);
+ return -1;
+ }
+
+ ComPtr<IOutputStream> stream;
+ HRESULT hr;
+ hr = m_socketObject->get_OutputStream(&stream);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ qint64 bytesWritten = writeIOStream(stream, data, maxSize);
+ if (bytesWritten < 0) {
+ qCWarning(QT_BT_WINRT) << "Socket::writeData: " << state;
+ errorString = QBluetoothSocket::tr("Cannot read while not connected");
+ q->setSocketError(QBluetoothSocket::OperationError);
+ }
+
+ emit q->bytesWritten(bytesWritten);
+ return bytesWritten;
+}
+
+qint64 QBluetoothSocketPrivate::readData(char *data, qint64 maxSize)
+{
+ Q_Q(QBluetoothSocket);
+
+ if (state != QBluetoothSocket::ConnectedState) {
+ errorString = QBluetoothSocket::tr("Cannot read while not connected");
+ q->setSocketError(QBluetoothSocket::OperationError);
+ return -1;
+ }
+
+ if (!buffer.isEmpty())
+ return buffer.read(data, maxSize);
+
+ return 0;
+}
+
+void QBluetoothSocketPrivate::close()
+{
+ 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);
+ return false;
+}
+
+qint64 QBluetoothSocketPrivate::bytesAvailable() const
+{
+ return buffer.size();
+}
+
+void QBluetoothSocketPrivate::handleNewData(const QVector<QByteArray> &data)
+{
+ // Defer putting the data into the list until the next event loop iteration
+ // (where the readyRead signal is emitted as well)
+ QMetaObject::invokeMethod(this, "putIntoPendingData", Qt::QueuedConnection,
+ Q_ARG(QVector<QByteArray>, data));
+}
+
+void QBluetoothSocketPrivate::handleError(QBluetoothSocket::SocketError error)
+{
+ Q_Q(QBluetoothSocket);
+ switch (error) {
+ case QBluetoothSocket::NetworkError:
+ errorString = QBluetoothSocket::tr("Network error");
+ break;
+ default:
+ errorString = QBluetoothSocket::tr("Unknown socket error");
+ }
+
+ q->setSocketError(error);
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+}
+
+void QBluetoothSocketPrivate::addToPendingData(const QVector<QByteArray> &data)
+{
+ Q_Q(QBluetoothSocket);
+ QMutexLocker locker(&m_readMutex);
+ m_pendingData.append(data);
+ for (const QByteArray &newData : data) {
+ char *writePointer = buffer.reserve(newData.length());
+ memcpy(writePointer, newData.data(), newData.length());
+ }
+ locker.unlock();
+ emit q->readyRead();
+}
+
+HRESULT QBluetoothSocketPrivate::handleConnectOpFinished(ABI::Windows::Foundation::IAsyncAction *action, ABI::Windows::Foundation::AsyncStatus status)
+{
+ Q_Q(QBluetoothSocket);
+ if (status != Completed || !m_connectOp) { // Protect against a late callback
+ errorString = QBluetoothSocket::tr("Unknown socket error");
+ q->setSocketError(QBluetoothSocket::UnknownSocketError);
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+ return S_OK;
+ }
+
+ HRESULT hr = action->GetResults();
+ switch (hr) {
+ case 0x8007274c: // A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
+ errorString = QBluetoothSocket::tr("Connection timed out");
+ q->setSocketError(QBluetoothSocket::NetworkError);
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+ return S_OK;
+ case 0x80072751: // A socket operation was attempted to an unreachable host.
+ errorString = QBluetoothSocket::tr("Host not reachable");
+ q->setSocketError(QBluetoothSocket::HostNotFoundError);
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+ return S_OK;
+ case 0x8007274d: // No connection could be made because the target machine actively refused it.
+ errorString = QBluetoothSocket::tr("Host refused connection");
+ q->setSocketError(QBluetoothSocket::HostNotFoundError);
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+ return S_OK;
+ default:
+ if (FAILED(hr)) {
+ errorString = QBluetoothSocket::tr("Unknown socket error");
+ q->setSocketError(QBluetoothSocket::UnknownSocketError);
+ q->setSocketState(QBluetoothSocket::UnconnectedState);
+ return S_OK;
+ }
+ }
+
+ // The callback might be triggered several times if we do not cancel/reset it here
+ if (m_connectOp) {
+ ComPtr<IAsyncInfo> info;
+ hr = m_connectOp.As(&info);
+ Q_ASSERT_SUCCEEDED(hr);
+ if (info) {
+ hr = info->Cancel();
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = info->Close();
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+ hr = m_connectOp.Reset();
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+
+ q->setSocketState(QBluetoothSocket::ConnectedState);
+ m_worker->startReading();
+ emit q->connected();
+
+ return S_OK;
+}
+
+QT_END_NAMESPACE
+
+#include "qbluetoothsocket_winrt.moc"
diff --git a/src/bluetooth/qlowenergyadvertisingdata.cpp b/src/bluetooth/qlowenergyadvertisingdata.cpp
index bf41c051..3837025b 100644
--- a/src/bluetooth/qlowenergyadvertisingdata.cpp
+++ b/src/bluetooth/qlowenergyadvertisingdata.cpp
@@ -129,6 +129,13 @@ QLowEnergyAdvertisingData &QLowEnergyAdvertisingData::operator=(const QLowEnergy
Specifies that \a name should be broadcast as the name of the device. If the full name does not
fit into the advertising data packet, an abbreviated name is sent, as described by the
Bluetooth Low Energy specification.
+
+ On Android, the local name cannot be changed. Android always uses the device name.
+ If this local name is not empty, the Android implementation includes the device name
+ in the advertisement packet; otherwise the device name is omitted from the advertisement
+ packet.
+
+ \sa localName()
*/
void QLowEnergyAdvertisingData::setLocalName(const QString &name)
{
@@ -137,6 +144,8 @@ void QLowEnergyAdvertisingData::setLocalName(const QString &name)
/*!
Returns the name of the local device that is to be advertised.
+
+ \sa setLocalName()
*/
QString QLowEnergyAdvertisingData::localName() const
{
diff --git a/src/bluetooth/qlowenergyconnectionparameters.cpp b/src/bluetooth/qlowenergyconnectionparameters.cpp
index af4502dd..95256402 100644
--- a/src/bluetooth/qlowenergyconnectionparameters.cpp
+++ b/src/bluetooth/qlowenergyconnectionparameters.cpp
@@ -68,6 +68,32 @@ public:
with each other. In general, a lower connection interval and latency means faster communication,
but also higher power consumption. How these criteria should be weighed against each other
is highly dependent on the concrete use case.
+
+ Android only indirectly permits the adjustment of this parameter set.
+ The platform separates the connection parameters into three categories (hight, low & balanced
+ priority). Each category implies a predefined set of values for \l minimumInterval(),
+ \l maximumInterval() and \l latency(). Additionally, the value ranges of each category can vary
+ from one Android device to the next. Qt uses the \l minimumInterval() to determine the target
+ category as follows:
+
+ \table
+ \header
+ \li minimumInterval()
+ \li Android priority
+ \row
+ \li interval < 30
+ \li CONNECTION_PRIORITY_HIGH
+ \row
+ \li 30 <= interval <= 100
+ \li CONNECTION_PRIORITY_BALANCED
+ \row
+ \li interval > 100
+ \li CONNECTION_PRIORITY_LOW_POWER
+ \endtable
+
+ The \l supervisionTimeout() cannot be changed on Android and is therefore ignored.
+
+
\inmodule QtBluetooth
\ingroup shared
@@ -163,6 +189,9 @@ int QLowEnergyConnectionParameters::latency() const
Sets the link supervision timeout to \a timeout milliseconds.
There are several constraints on this value: It must be in the range [100,32000] and it must be
larger than (1 + \l latency()) * 2 * \l maximumInterval().
+
+ On Android, this timeout is not adjustable and therefore ignored.
+
\sa supervisionTimeout()
*/
void QLowEnergyConnectionParameters::setSupervisionTimeout(int timeout)
diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp
index 03278276..6a9b50a0 100644
--- a/src/bluetooth/qlowenergycontroller.cpp
+++ b/src/bluetooth/qlowenergycontroller.cpp
@@ -359,7 +359,12 @@ void QLowEnergyControllerPrivate::invalidateServices()
QSharedPointer<QLowEnergyServicePrivate> QLowEnergyControllerPrivate::serviceForHandle(
QLowEnergyHandle handle)
{
- foreach (QSharedPointer<QLowEnergyServicePrivate> service, serviceList.values())
+ ServiceDataMap& currentList = serviceList;
+ if (role == QLowEnergyController::PeripheralRole)
+ currentList = localServices;
+
+ const QList<QSharedPointer<QLowEnergyServicePrivate>> values = currentList.values();
+ for (auto service: values)
if (service->startHandle <= handle && handle <= service->endHandle)
return service;
@@ -963,19 +968,35 @@ QLowEnergyService *QLowEnergyController::addService(const QLowEnergyServiceData
/*!
Requests the controller to update the connection according to \a parameters.
If the request is successful, the \l connectionUpdated() signal will be emitted
- with the actual new parameters.
- See the \l QLowEnergyConnectionParameters class for more information on connection parameters.
- \note Currently, this functionality is only implemented on Linux.
-
+ with the actual new parameters. See the \l QLowEnergyConnectionParameters class for more
+ information on connection parameters.
+
+ Android only indirectly permits the adjustment of this parameter set.
+ The connection parameters are separated into three categories (high, low & balanced priority).
+ Each category implies a pre-configured set of values for
+ \l QLowEnergyConnectionParameters::minimumInterval(),
+ \l QLowEnergyConnectionParameters::maximumInterval() and
+ \l QLowEnergyConnectionParameters::latency(). Although the connection request is an asynchronous
+ operation, Android does not provide a callback stating the result of the request. This is
+ an acknowledged Android bug. Due to this bug Android does not emit the \l connectionUpdated()
+ signal.
+
+ \note Currently, this functionality is only implemented on Linux and Android.
+
+ \sa connectionUpdated()
\since 5.7
*/
void QLowEnergyController::requestConnectionUpdate(const QLowEnergyConnectionParameters &parameters)
{
- if (state() != ConnectedState) {
+ switch (state()) {
+ case ConnectedState:
+ case DiscoveredState:
+ case DiscoveringState:
+ d_ptr->requestConnectionUpdate(parameters);
+ break;
+ default:
qCWarning(QT_BT) << "Connection update request only possible in connected state";
- return;
}
- d_ptr->requestConnectionUpdate(parameters);
}
/*!
diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp
index cd6603a3..0d63faf1 100644
--- a/src/bluetooth/qlowenergycontroller_android.cpp
+++ b/src/bluetooth/qlowenergycontroller_android.cpp
@@ -40,6 +40,13 @@
#include "qlowenergycontroller_p.h"
#include <QtCore/QLoggingCategory>
#include <QtAndroidExtras/QAndroidJniEnvironment>
+#include <QtBluetooth/QLowEnergyServiceData>
+#include <QtBluetooth/QLowEnergyCharacteristicData>
+#include <QtBluetooth/QLowEnergyDescriptorData>
+#include <QtBluetooth/QLowEnergyAdvertisingData>
+#include <QtBluetooth/QLowEnergyAdvertisingParameters>
+#include <QtBluetooth/QLowEnergyConnectionParameters>
+
QT_BEGIN_NAMESPACE
@@ -56,25 +63,42 @@ QLowEnergyControllerPrivate::QLowEnergyControllerPrivate()
QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate()
{
+ if (role == QLowEnergyController::PeripheralRole) {
+ if (hub)
+ hub->javaObject().callMethod<void>("disconnectServer");
+ }
}
void QLowEnergyControllerPrivate::init()
{
-}
-
-void QLowEnergyControllerPrivate::connectToDevice()
-{
- // required to pass unit test on default backend
- if (remoteDevice.isNull()) {
- qWarning() << "Invalid/null remote device address";
- setError(QLowEnergyController::UnknownRemoteDeviceError);
- return;
- }
+ // Android Central/Client support starts with v18
+ // Peripheral/Server support requires Android API v21
+ const bool isPeripheral = (role == QLowEnergyController::PeripheralRole);
+ const jint version = QtAndroidPrivate::androidSdkVersion();
+
+ if (isPeripheral) {
+ if (version < 21) {
+ qWarning() << "Qt Bluetooth LE Peripheral support not available"
+ "on Android devices below version 21";
+ return;
+ }
- setState(QLowEnergyController::ConnectingState);
+ hub = new LowEnergyNotificationHub(remoteDevice, isPeripheral, this);
+ // we only connect to the peripheral role specific signals
+ // TODO add connections as they get added later on
+ connect(hub, &LowEnergyNotificationHub::connectionUpdated,
+ this, &QLowEnergyControllerPrivate::connectionUpdated);
+ connect(hub, &LowEnergyNotificationHub::advertisementError,
+ this, &QLowEnergyControllerPrivate::advertisementError);
+ } else {
+ if (version < 18) {
+ qWarning() << "Qt Bluetooth LE Central/Client support not available"
+ "on Android devices below version 18";
+ return;
+ }
- if (!hub) {
- hub = new LowEnergyNotificationHub(remoteDevice, this);
+ hub = new LowEnergyNotificationHub(remoteDevice, isPeripheral, this);
+ // we only connect to the central role specific signals
connect(hub, &LowEnergyNotificationHub::connectionUpdated,
this, &QLowEnergyControllerPrivate::connectionUpdated);
connect(hub, &LowEnergyNotificationHub::servicesDiscovered,
@@ -94,6 +118,21 @@ void QLowEnergyControllerPrivate::connectToDevice()
connect(hub, &LowEnergyNotificationHub::serviceError,
this, &QLowEnergyControllerPrivate::serviceError);
}
+}
+
+void QLowEnergyControllerPrivate::connectToDevice()
+{
+ if (!hub)
+ return; // Android version below v18
+
+ // required to pass unit test on default backend
+ if (remoteDevice.isNull()) {
+ qWarning() << "Invalid/null remote device address";
+ setError(QLowEnergyController::UnknownRemoteDeviceError);
+ return;
+ }
+
+ setState(QLowEnergyController::ConnectingState);
if (!hub->javaObject().isValid()) {
qCWarning(QT_BT_ANDROID) << "Cannot initiate QtBluetoothLE";
@@ -306,14 +345,56 @@ void QLowEnergyControllerPrivate::connectionUpdated(
QLowEnergyController::ControllerState newState,
QLowEnergyController::Error errorCode)
{
- Q_Q(QLowEnergyController);
-
- const QLowEnergyController::ControllerState oldState = state;
qCDebug(QT_BT_ANDROID) << "Connection updated:"
<< "error:" << errorCode
- << "oldState:" << oldState
+ << "oldState:" << state
<< "newState:" << newState;
+ if (role == QLowEnergyController::PeripheralRole)
+ peripheralConnectionUpdated(newState, errorCode);
+ else
+ centralConnectionUpdated(newState, errorCode);
+}
+
+// called if server/peripheral
+void QLowEnergyControllerPrivate::peripheralConnectionUpdated(
+ QLowEnergyController::ControllerState newState,
+ QLowEnergyController::Error errorCode)
+{
+ // Java errorCode can be larger than max QLowEnergyController::Error
+ if (errorCode > QLowEnergyController::AdvertisingError)
+ errorCode = QLowEnergyController::UnknownError;
+
+ if (errorCode != QLowEnergyController::NoError)
+ setError(errorCode);
+
+ const QLowEnergyController::ControllerState oldState = state;
+ setState(newState);
+
+ // disconnect implies stop of advertisement
+ if (newState == QLowEnergyController::UnconnectedState)
+ stopAdvertising();
+
+
+ Q_Q(QLowEnergyController);
+ if (oldState == QLowEnergyController::ConnectedState
+ && newState != QLowEnergyController::ConnectedState) {
+ emit q->disconnected();
+ } else if (newState == QLowEnergyController::ConnectedState
+ && oldState != QLowEnergyController::ConnectedState) {
+ emit q->connected();
+ }
+}
+
+// called if client/central
+void QLowEnergyControllerPrivate::centralConnectionUpdated(
+ QLowEnergyController::ControllerState newState,
+ QLowEnergyController::Error errorCode)
+{
+ Q_Q(QLowEnergyController);
+
+ const QLowEnergyController::ControllerState oldState = state;
+
if (errorCode != QLowEnergyController::NoError) {
// ConnectionError if transition from Connecting to Connected
if (oldState == QLowEnergyController::ConnectingState) {
@@ -599,32 +680,433 @@ void QLowEnergyControllerPrivate::serviceError(
service->setError(errorCode);
}
+void QLowEnergyControllerPrivate::advertisementError(int errorCode)
+{
+ Q_Q(QLowEnergyController);
+
+ switch (errorCode)
+ {
+ case 1: // AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE
+ errorString = QLowEnergyController::tr("Advertisement data is larger than 31 bytes");
+ break;
+ case 2: // AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED
+ errorString = QLowEnergyController::tr("Advertisement feature not supported on the platform");
+ break;
+ case 3: // AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR
+ errorString = QLowEnergyController::tr("Error occurred trying to start advertising");
+ break;
+ case 4: // AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS
+ errorString = QLowEnergyController::tr("Failed due to too many advertisers");
+ break;
+ default:
+ errorString = QLowEnergyController::tr("Unknown advertisment error");
+ break;
+ }
+
+ error = QLowEnergyController::AdvertisingError;
+ emit q->error(error);
+
+ // not relevant states in peripheral mode
+ Q_ASSERT(state != QLowEnergyController::DiscoveredState);
+ Q_ASSERT(state != QLowEnergyController::DiscoveringState);
+
+ switch (state)
+ {
+ case QLowEnergyController::UnconnectedState:
+ case QLowEnergyController::ConnectingState:
+ case QLowEnergyController::ConnectedState:
+ case QLowEnergyController::ClosingState:
+ // noop as remote is already connected or about to disconnect.
+ // when connection drops we reset to unconnected anyway
+ break;
+
+ case QLowEnergyController::AdvertisingState:
+ setState(QLowEnergyController::UnconnectedState);
+ break;
+ default:
+ break;
+ }
+}
+
+static QAndroidJniObject javaParcelUuidfromQtUuid(const QBluetoothUuid& uuid)
+{
+ QString output = uuid.toString();
+ // cut off leading and trailing brackets
+ output = output.mid(1, output.size()-2);
+
+ QAndroidJniObject javaString = QAndroidJniObject::fromString(output);
+ QAndroidJniObject parcelUuid = QAndroidJniObject::callStaticObjectMethod(
+ "android/os/ParcelUuid", "fromString",
+ "(Ljava/lang/String;)Landroid/os/ParcelUuid;", javaString.object());
+
+ return parcelUuid;
+}
+
+static QAndroidJniObject createJavaAdvertiseData(const QLowEnergyAdvertisingData &data)
+{
+ QAndroidJniObject builder = QAndroidJniObject("android/bluetooth/le/AdvertiseData$Builder");
+
+ // device name cannot be set but there is choice to show it or not
+ builder = builder.callObjectMethod("setIncludeDeviceName", "(Z)Landroid/bluetooth/le/AdvertiseData$Builder;",
+ !data.localName().isEmpty());
+ builder = builder.callObjectMethod("setIncludeTxPowerLevel", "(Z)Landroid/bluetooth/le/AdvertiseData$Builder;",
+ data.includePowerLevel());
+ for (const auto service: data.services())
+ {
+ builder = builder.callObjectMethod("addServiceUuid",
+ "(Landroid/os/ParcelUuid;)Landroid/bluetooth/le/AdvertiseData$Builder;",
+ javaParcelUuidfromQtUuid(service).object());
+ }
+
+ if (!data.manufacturerData().isEmpty()) {
+ QAndroidJniEnvironment env;
+ const qint32 nativeSize = data.manufacturerData().size();
+ jbyteArray nativeData = env->NewByteArray(nativeSize);
+ env->SetByteArrayRegion(nativeData, 0, nativeSize,
+ reinterpret_cast<const jbyte*>(data.manufacturerData().constData()));
+ builder = builder.callObjectMethod("addManufacturerData",
+ "(I[B])Landroid/bluetooth/le/AdvertiseData$Builder;",
+ data.manufacturerId(), nativeData);
+ env->DeleteLocalRef(nativeData);
+
+ if (env->ExceptionCheck()) {
+ qCWarning(QT_BT_ANDROID) << "Cannot set manufacturer id/data";
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ }
+
+ /*// TODO Qt vs Java API mismatch
+ -> Qt assumes rawData() is a global field
+ -> Android pairs rawData() per service uuid
+ if (!data.rawData().isEmpty()) {
+ QAndroidJniEnvironment env;
+ qint32 nativeSize = data.rawData().size();
+ jbyteArray nativeData = env->NewByteArray(nativeSize);
+ env->SetByteArrayRegion(nativeData, 0, nativeSize,
+ reinterpret_cast<const jbyte*>(data.rawData().constData()));
+ builder = builder.callObjectMethod("addServiceData",
+ "(Landroid/os/ParcelUuid;[B])Landroid/bluetooth/le/AdvertiseData$Builder;",
+ data.rawData().object(), nativeData);
+ env->DeleteLocalRef(nativeData);
+
+ if (env->ExceptionCheck()) {
+ qCWarning(QT_BT_ANDROID) << "Cannot set advertisement raw data";
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ }*/
+
+ QAndroidJniObject javaAdvertiseData = builder.callObjectMethod("build",
+ "()Landroid/bluetooth/le/AdvertiseData;");
+ return javaAdvertiseData;
+}
+
+static QAndroidJniObject createJavaAdvertiseSettings(const QLowEnergyAdvertisingParameters& params)
+{
+ QAndroidJniObject builder = QAndroidJniObject("android/bluetooth/le/AdvertiseSettings$Builder");
+
+ bool connectable = false;
+ switch (params.mode())
+ {
+ case QLowEnergyAdvertisingParameters::AdvInd:
+ connectable = true;
+ break;
+ case QLowEnergyAdvertisingParameters::AdvScanInd:
+ case QLowEnergyAdvertisingParameters::AdvNonConnInd:
+ connectable = false;
+ break;
+ // intentionally no default case
+ }
+ builder = builder.callObjectMethod("setConnectable", "(Z)Landroid/bluetooth/le/AdvertiseSettings$Builder;",
+ connectable);
+
+ /* TODO No Android API for further QLowEnergyAdvertisingParameters options
+ * Android TxPowerLevel, AdvertiseMode and Timeout not mappable to Qt
+ */
+
+ QAndroidJniObject javaAdvertiseSettings = builder.callObjectMethod("build",
+ "()Landroid/bluetooth/le/AdvertiseSettings;");
+ return javaAdvertiseSettings;
+}
+
+
void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &params,
const QLowEnergyAdvertisingData &advertisingData,
const QLowEnergyAdvertisingData &scanResponseData)
{
- Q_UNUSED(params);
- Q_UNUSED(advertisingData);
- Q_UNUSED(scanResponseData);
- qCWarning(QT_BT_ANDROID) << "LE advertising not implemented for Android";
+ setState(QLowEnergyController::AdvertisingState);
+
+ if (!hub->javaObject().isValid()) {
+ qCWarning(QT_BT_ANDROID) << "Cannot initiate QtBluetoothLEServer";
+ setError(QLowEnergyController::AdvertisingError);
+ setState(QLowEnergyController::UnconnectedState);
+ return;
+ }
+
+ // Pass on advertisingData, scanResponse & AdvertiseSettings
+ QAndroidJniObject jAdvertiseData = createJavaAdvertiseData(advertisingData);
+ QAndroidJniObject jScanResponse = createJavaAdvertiseData(scanResponseData);
+ QAndroidJniObject jAdvertiseSettings = createJavaAdvertiseSettings(params);
+
+ const bool result = hub->javaObject().callMethod<jboolean>("startAdvertising",
+ "(Landroid/bluetooth/le/AdvertiseData;Landroid/bluetooth/le/AdvertiseData;Landroid/bluetooth/le/AdvertiseSettings;)Z",
+ jAdvertiseData.object(), jScanResponse.object(), jAdvertiseSettings.object());
+ if (!result) {
+ setError(QLowEnergyController::AdvertisingError);
+ setState(QLowEnergyController::UnconnectedState);
+ }
}
void QLowEnergyControllerPrivate::stopAdvertising()
{
- qCWarning(QT_BT_ANDROID) << "LE advertising not implemented for Android";
+ setState(QLowEnergyController::UnconnectedState);
+ hub->javaObject().callMethod<void>("stopAdvertising");
}
void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnectionParameters &params)
{
- Q_UNUSED(params);
- qCWarning(QT_BT_ANDROID) << "Connection update not implemented for Android";
+ // Possible since Android v21
+ // Android does not permit specification of specific latency or min/max
+ // connection intervals (see BluetoothGatt.requestConnectionPriority()
+ // In fact, each device manufacturer is permitted to change those values via a config
+ // file too. Therefore we can only make an approximated guess (see implementation below)
+ // In addition there is no feedback signal (known bug) from the hardware layer as per v24.
+
+ // TODO recheck in later Android releases whether callback for
+ // BluetoothGatt.requestConnectionPriority() was added
+
+ if (role != QLowEnergyController::CentralRole) {
+ qCWarning(QT_BT_ANDROID) << "On Android, connection requests only work for central role";
+ return;
+ }
+
+ bool result = hub->javaObject().callMethod<jboolean>("requestConnectionUpdatePriority",
+ "(D)Z", params.minimumInterval());
+ if (!result)
+ qCWarning(QT_BT_ANDROID) << "Cannot set connection update priority";
+}
+
+// Conversion: QBluetoothUuid -> java.util.UUID
+static QAndroidJniObject javaUuidfromQtUuid(const QBluetoothUuid& uuid)
+{
+ QString output = uuid.toString();
+ // cut off leading and trailing brackets
+ output = output.mid(1, output.size()-2);
+
+ QAndroidJniObject javaString = QAndroidJniObject::fromString(output);
+ QAndroidJniObject javaUuid = QAndroidJniObject::callStaticObjectMethod(
+ "java/util/UUID", "fromString", "(Ljava/lang/String;)Ljava/util/UUID;",
+ javaString.object());
+
+ return javaUuid;
}
-void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &service,
+/*
+ * Returns the Java char permissions based on the given characteristic data.
+ */
+static int setupCharPermissions(const QLowEnergyCharacteristicData &charData)
+{
+ int permission = 0;
+ if (charData.properties() & QLowEnergyCharacteristic::Read) {
+ if (int(charData.readConstraints()) == 0 // nothing is equivalent to simple read
+ || (charData.readConstraints() & QBluetooth::AttAuthorizationRequired)) {
+ permission |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattCharacteristic",
+ "PERMISSION_READ");
+ }
+
+ if (charData.readConstraints() & QBluetooth::AttAuthenticationRequired) {
+ permission |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattCharacteristic",
+ "PERMISSION_READ_ENCRYPTED");
+ }
+
+ if (charData.readConstraints() & QBluetooth::AttEncryptionRequired) {
+ permission |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattCharacteristic",
+ "PERMISSION_READ_ENCRYPTED_MITM");
+ }
+ }
+
+ if (charData.properties() &
+ (QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse) ) {
+ if (((int)charData.writeConstraints()) == 0 // no flag is equivalent ti simple write
+ || (charData.writeConstraints() & QBluetooth::AttAuthorizationRequired)) {
+ permission |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattCharacteristic",
+ "PERMISSION_WRITE");
+ }
+
+ if (charData.writeConstraints() & QBluetooth::AttAuthenticationRequired) {
+ permission |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattCharacteristic",
+ "PERMISSION_WRITE_ENCRYPTED");
+ }
+
+ if (charData.writeConstraints() & QBluetooth::AttEncryptionRequired) {
+ permission |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattCharacteristic",
+ "PERMISSION_WRITE_ENCRYPTED_MITM");
+ }
+ }
+
+ if (charData.properties() & QLowEnergyCharacteristic::WriteSigned) {
+ if (charData.writeConstraints() & QBluetooth::AttEncryptionRequired) {
+ permission |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattCharacteristic",
+ "PERMISSION_WRITE_SIGNED_MITM");
+ } else {
+ permission |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattCharacteristic",
+ "PERMISSION_WRITE_SIGNED");
+ }
+ }
+
+ return permission;
+}
+
+/*
+ * Returns the Java desc permissions based on the given descriptor data.
+ */
+static int setupDescPermissions(const QLowEnergyDescriptorData &descData)
+{
+ int permissions = 0;
+
+ if (descData.isReadable()) {
+ if (int(descData.readConstraints()) == 0 // empty is equivalent to simple read
+ || (descData.readConstraints() & QBluetooth::AttAuthorizationRequired)) {
+ permissions |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattDescriptor",
+ "PERMISSION_READ");
+ }
+
+ if (descData.readConstraints() & QBluetooth::AttAuthenticationRequired) {
+ permissions |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattDescriptor",
+ "PERMISSION_READ_ENCRYPTED");
+ }
+
+ if (descData.readConstraints() & QBluetooth::AttEncryptionRequired) {
+ permissions |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattDescriptor",
+ "PERMISSION_READ_ENCRYPTED_MITM");
+ }
+ }
+
+ if (descData.isWritable()) {
+ if (((int)descData.readConstraints()) == 0 // empty is equivalent to simple read
+ || (descData.readConstraints() & QBluetooth::AttAuthorizationRequired)) {
+ permissions |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattDescriptor",
+ "PERMISSION_WRITE");
+ }
+
+ if (descData.readConstraints() & QBluetooth::AttAuthenticationRequired) {
+ permissions |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattDescriptor",
+ "PERMISSION_WRITE_ENCRYPTED");
+ }
+
+ if (descData.readConstraints() & QBluetooth::AttEncryptionRequired) {
+ permissions |= QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattDescriptor",
+ "PERMISSION_WRITE_ENCRYPTED_MITM");
+ }
+ }
+
+ return permissions;
+}
+
+void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &serviceData,
QLowEnergyHandle startHandle)
{
- Q_UNUSED(service);
- Q_UNUSED(startHandle);
+ QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(startHandle);
+ if (service.isNull())
+ return;
+
+ // create BluetoothGattService object
+ jint sType = QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattService", "SERVICE_TYPE_PRIMARY");
+ if (serviceData.type() == QLowEnergyServiceData::ServiceTypeSecondary)
+ sType = QAndroidJniObject::getStaticField<jint>(
+ "android/bluetooth/BluetoothGattService", "SERVICE_TYPE_SECONDARY");
+
+ service->androidService = QAndroidJniObject("android/bluetooth/BluetoothGattService",
+ "(Ljava/util/UUID;I)V",
+ javaUuidfromQtUuid(service->uuid).object(), sType);
+
+ // add included services, which must have been added earlier already
+ const QList<QLowEnergyService*> includedServices = serviceData.includedServices();
+ for (const auto includedServiceEntry: includedServices) {
+ //TODO test this end-to-end
+ const jboolean result = service->androidService.callMethod<jboolean>(
+ "addService", "(Landroid/bluetooth/BluetoothGattService;)Z",
+ includedServiceEntry->d_ptr->androidService.object());
+ if (!result)
+ qWarning(QT_BT_ANDROID) << "Cannot add included service " << includedServiceEntry->serviceUuid()
+ << "to current service" << service->uuid;
+ }
+
+ // add characteristics
+ const QList<QLowEnergyCharacteristicData> serviceCharsData = serviceData.characteristics();
+ for (const auto &charData: serviceCharsData) {
+ QAndroidJniObject javaChar = QAndroidJniObject("android/bluetooth/BluetoothGattCharacteristic",
+ "(Ljava/util/UUID;II)V",
+ javaUuidfromQtUuid(charData.uuid()).object(),
+ int(charData.properties()),
+ setupCharPermissions(charData));
+
+ QAndroidJniEnvironment env;
+ jbyteArray jb = env->NewByteArray(charData.value().size());
+ env->SetByteArrayRegion(jb, 0, charData.value().size(), (jbyte*)charData.value().data());
+ jboolean success = javaChar.callMethod<jboolean>("setValue", "([B)Z", jb);
+ if (!success)
+ qCWarning(QT_BT_ANDROID) << "Cannot setup initial characteristic value for " << charData.uuid();
+
+ env->DeleteLocalRef(jb);
+
+ const QList<QLowEnergyDescriptorData> descriptorList = charData.descriptors();
+ for (const auto &descData: descriptorList) {
+ QAndroidJniObject javaDesc = QAndroidJniObject("android/bluetooth/BluetoothGattDescriptor",
+ "(Ljava/util/UUID;I)V",
+ javaUuidfromQtUuid(descData.uuid()).object(),
+ setupDescPermissions(descData));
+
+ jb = env->NewByteArray(descData.value().size());
+ env->SetByteArrayRegion(jb, 0, descData.value().size(), (jbyte*)descData.value().data());
+ success = javaDesc.callMethod<jboolean>("setValue", "([B)Z", jb);
+ if (!success) {
+ qCWarning(QT_BT_ANDROID) << "Cannot setup initial descriptor value for "
+ << descData.uuid() << "(char" << charData.uuid()
+ << "on service " << service->uuid << ")";
+ }
+
+ env->DeleteLocalRef(jb);
+
+
+ success = javaChar.callMethod<jboolean>("addDescriptor",
+ "(Landroid/bluetooth/BluetoothGattDescriptor;)Z",
+ javaDesc.object());
+ if (!success) {
+ qCWarning(QT_BT_ANDROID) << "Cannot add descriptor" << descData.uuid()
+ << "to service" << service->uuid << "(char:"
+ << charData.uuid() << ")";
+ }
+ }
+
+ success = service->androidService.callMethod<jboolean>(
+ "addCharacteristic",
+ "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", javaChar.object());
+ if (!success) {
+ qCWarning(QT_BT_ANDROID) << "Cannot add characteristic" << charData.uuid()
+ << "to service" << service->uuid;
+ }
+ }
+
+ hub->javaObject().callMethod<void>("addService",
+ "(Landroid/bluetooth/BluetoothGattService;)V",
+ service->androidService.object());
}
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index 19d10567..bb12c933 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -184,10 +184,11 @@ public:
QLowEnergyController::Error error;
QString errorString;
- // list of all found service uuids
+ // list of all found service uuids on remote device
ServiceDataMap serviceList;
QLowEnergyHandle lastLocalHandle;
+ // list of all service uuids on local peripheral device
ServiceDataMap localServices;
struct Attribute {
@@ -441,6 +442,14 @@ private slots:
QLowEnergyService::ServiceError errorCode);
void characteristicChanged(int charHandle, const QByteArray &data);
void serviceError(int attributeHandle, QLowEnergyService::ServiceError errorCode);
+ void advertisementError(int errorCode);
+
+private:
+ void peripheralConnectionUpdated(QLowEnergyController::ControllerState newState,
+ QLowEnergyController::Error errorCode);
+ void centralConnectionUpdated(QLowEnergyController::ControllerState newState,
+ QLowEnergyController::Error errorCode);
+
#elif defined(QT_WINRT_BLUETOOTH)
private slots:
void characteristicChanged(int charHandle, const QByteArray &data);
diff --git a/src/bluetooth/qlowenergyserviceprivate_p.h b/src/bluetooth/qlowenergyserviceprivate_p.h
index bde099ba..7727b583 100644
--- a/src/bluetooth/qlowenergyserviceprivate_p.h
+++ b/src/bluetooth/qlowenergyserviceprivate_p.h
@@ -57,6 +57,10 @@
#include <QtBluetooth/QLowEnergyService>
#include <QtBluetooth/QLowEnergyCharacteristic>
+#if defined(QT_ANDROID_BLUETOOTH)
+#include <QtAndroidExtras/QAndroidJniObject>
+#endif
+
QT_BEGIN_NAMESPACE
class QLowEnergyControllerPrivate;
@@ -119,6 +123,12 @@ public:
QHash<QLowEnergyHandle, CharData> characteristicList;
QPointer<QLowEnergyControllerPrivate> controller;
+
+#if defined(QT_ANDROID_BLUETOOTH)
+ // reference to the BluetoothGattService object
+ QAndroidJniObject androidService;
+#endif
+
};
typedef QHash<QLowEnergyHandle, QLowEnergyServicePrivate::CharData> CharacteristicDataMap;
diff --git a/src/nfc/nfc.pro b/src/nfc/nfc.pro
index 0819cc4f..ce193efa 100644
--- a/src/nfc/nfc.pro
+++ b/src/nfc/nfc.pro
@@ -74,7 +74,8 @@ linux:!android:qtHaveModule(dbus) {
qllcpserver_p.cpp \
qnearfieldsharemanagerimpl_p.cpp \
qnearfieldsharetargetimpl_p.cpp \
- qnearfieldmanager_neard.cpp
+ qnearfieldmanager_neard.cpp \
+ qnearfieldtarget_neard_p.cpp
include(neard/neard.pri)
@@ -107,6 +108,7 @@ linux:!android:qtHaveModule(dbus) {
android/androidjninfc.cpp \
qnearfieldmanager_android.cpp \
qnearfieldtarget_android.cpp \
+ qnearfieldtarget_android_p.cpp \
qnearfieldsharemanagerimpl_p.cpp \
qnearfieldsharetargetimpl_p.cpp \
android/androidmainnewintentlistener.cpp
@@ -127,7 +129,8 @@ isEmpty(NFC_BACKEND_AVAILABLE) {
qllcpserver_p.cpp \
qnearfieldmanagerimpl_p.cpp \
qnearfieldsharemanagerimpl_p.cpp \
- qnearfieldsharetargetimpl_p.cpp
+ qnearfieldsharetargetimpl_p.cpp \
+ qnearfieldtarget_p.cpp
}
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS
diff --git a/src/nfc/qnearfieldtarget.cpp b/src/nfc/qnearfieldtarget.cpp
index 509160c1..dff00a0b 100644
--- a/src/nfc/qnearfieldtarget.cpp
+++ b/src/nfc/qnearfieldtarget.cpp
@@ -111,7 +111,7 @@ QT_BEGIN_NAMESPACE
/*!
\enum QNearFieldTarget::Error
- This enum describes the error codes that that a near field target reports.
+ This enum describes the error codes that a near field target reports.
\value NoError No error has occurred.
\value UnknownError An unidentified error occurred.
@@ -123,17 +123,10 @@ QT_BEGIN_NAMESPACE
\value InvalidParametersError Invalid parameters were passed to a tag type specific function.
\value NdefReadError Failed to read NDEF messages from the target.
\value NdefWriteError Failed to write NDEF messages to the target.
+ \value CommandError Failed to send a command to the target.
*/
-// Copied from qbytearray.cpp
-// Modified to initialize the crc with 0x6363 instead of 0xffff and to not invert the final result.
-static const quint16 crc_tbl[16] = {
- 0x0000, 0x1081, 0x2102, 0x3183,
- 0x4204, 0x5285, 0x6306, 0x7387,
- 0x8408, 0x9489, 0xa50a, 0xb58b,
- 0xc60c, 0xd68d, 0xe70e, 0xf78f
-};
-
+#if QT_DEPRECATED_SINCE(5, 9)
/*!
\relates QNearFieldTarget
@@ -141,17 +134,9 @@ static const quint16 crc_tbl[16] = {
*/
quint16 qNfcChecksum(const char *data, uint len)
{
- quint16 crc = 0x6363;
- uchar c;
- const uchar *p = reinterpret_cast<const uchar *>(data);
- while (len--) {
- c = *p++;
- crc = ((crc >> 4) & 0x0fff) ^ crc_tbl[((crc ^ c) & 15)];
- c >>= 4;
- crc = ((crc >> 4) & 0x0fff) ^ crc_tbl[((crc ^ c) & 15)];
- }
- return crc;
+ return qChecksum(data, len, Qt::ChecksumItuV41);
}
+#endif
/*!
\fn void QNearFieldTarget::disconnected()
@@ -276,7 +261,7 @@ QNearFieldTarget::RequestId &QNearFieldTarget::RequestId::operator=(const Reques
Constructs a new near field target with \a parent.
*/
QNearFieldTarget::QNearFieldTarget(QObject *parent)
-: QObject(parent), d_ptr(new QNearFieldTargetPrivate)
+: QObject(parent), d_ptr(new QNearFieldTargetPrivate(this))
{
qRegisterMetaType<QNearFieldTarget::RequestId>();
qRegisterMetaType<QNearFieldTarget::Error>();
@@ -318,6 +303,52 @@ QUrl QNearFieldTarget::url() const
*/
/*!
+ \since 5.9
+
+ Returns true if this feature is enabled.
+
+ \sa setKeepConnection(), disconnect()
+*/
+bool QNearFieldTarget::keepConnection() const
+{
+ return d_ptr->keepConnection();
+}
+
+/*!
+ \since 5.9
+
+ Causes QNearFieldTarget to keep the connection after processing a command
+ or reading/writing NDEF messages. A call of this function is only needed once.
+
+ Returns true if enabling this feature was successful. A possible
+ reason for a failure is the lack of support on the used platform.
+
+ Enabling this feature requires to use the disconnect() function too, to close the
+ connection manually and enable communication with the target from a different instance.
+ Disabling this feature will also close an open connection.
+
+ \sa keepConnection(), disconnect()
+*/
+bool QNearFieldTarget::setKeepConnection(bool isPersistent)
+{
+ return d_ptr->setKeepConnection(isPersistent);
+}
+
+/*!
+ \since 5.9
+
+ Closes the connection to the target.
+
+ Returns true only if an existing connection was successfully closed.
+
+ \sa keepConnection(), setKeepConnection()
+*/
+bool QNearFieldTarget::disconnect()
+{
+ return d_ptr->disconnect();
+}
+
+/*!
Returns true if the target is processing commands; otherwise returns false.
*/
bool QNearFieldTarget::isProcessingCommand() const
@@ -364,6 +395,19 @@ QNearFieldTarget::RequestId QNearFieldTarget::writeNdefMessages(const QList<QNde
}
/*!
+ \since 5.9
+
+ Returns the maximum number of bytes that can be sent with sendCommand. 0 will
+ be returned if the target does not support sending tag type specific commands.
+
+ \sa sendCommand(), sendCommands()
+*/
+int QNearFieldTarget::maxCommandLength() const
+{
+ return d_ptr->maxCommandLength();
+}
+
+/*!
Sends \a command to the near field target. Returns a request id which can be used to track the
completion status of the request. An invalid request id will be returned if the target does not
support sending tag type specific commands.
diff --git a/src/nfc/qnearfieldtarget.h b/src/nfc/qnearfieldtarget.h
index dc081f5e..641645c3 100644
--- a/src/nfc/qnearfieldtarget.h
+++ b/src/nfc/qnearfieldtarget.h
@@ -40,9 +40,10 @@
#ifndef QNEARFIELDTARGET_H
#define QNEARFIELDTARGET_H
-#include <QtCore/QObject>
+#include <QtCore/QByteArray>
#include <QtCore/QList>
#include <QtCore/QMetaType>
+#include <QtCore/QObject>
#include <QtCore/QSharedDataPointer>
#include <QtNfc/qnfcglobal.h>
@@ -91,7 +92,8 @@ public:
ChecksumMismatchError,
InvalidParametersError,
NdefReadError,
- NdefWriteError
+ NdefWriteError,
+ CommandError
};
Q_ENUM(Error)
@@ -125,6 +127,10 @@ public:
virtual Type type() const = 0;
virtual AccessMethods accessMethods() const = 0;
+ bool keepConnection() const;
+ bool setKeepConnection(bool isPersistent);
+ bool disconnect();
+
bool isProcessingCommand() const;
// NdefAccess
@@ -133,6 +139,7 @@ public:
virtual RequestId writeNdefMessages(const QList<QNdefMessage> &messages);
// TagTypeSpecificAccess
+ int maxCommandLength() const;
virtual RequestId sendCommand(const QByteArray &command);
virtual RequestId sendCommands(const QList<QByteArray> &commands);
@@ -160,7 +167,9 @@ private:
QNearFieldTargetPrivate *d_ptr;
};
+#if QT_DEPRECATED_SINCE(5, 9)
Q_NFC_EXPORT quint16 qNfcChecksum(const char * data, uint len);
+#endif
Q_DECLARE_OPERATORS_FOR_FLAGS(QNearFieldTarget::AccessMethods)
diff --git a/src/nfc/qnearfieldtarget_android.cpp b/src/nfc/qnearfieldtarget_android.cpp
index e0c1616d..04718fc5 100644
--- a/src/nfc/qnearfieldtarget_android.cpp
+++ b/src/nfc/qnearfieldtarget_android.cpp
@@ -41,25 +41,27 @@
#include "android/androidjninfc_p.h"
#include "qdebug.h"
-#define NDEFTECHNOLOGY "android.nfc.tech.Ndef"
-#define NDEFFORMATABLETECHNOLOGY "android.nfc.tech.NdefFormatable"
-#define NFCATECHNOLOGY "android.nfc.tech.NfcA"
-#define NFCBTECHNOLOGY "android.nfc.tech.NfcB"
-#define NFCFTECHNOLOGY "android.nfc.tech.NfcF"
-#define NFCVTECHNOLOGY "android.nfc.tech.NfcV"
-#define MIFARECLASSICTECHNOLOGY "android.nfc.tech.MifareClassic"
-#define MIFARECULTRALIGHTTECHNOLOGY "android.nfc.tech.MifareUltralight"
-
-#define MIFARETAG "com.nxp.ndef.mifareclassic"
-#define NFCTAGTYPE1 "org.nfcforum.ndef.type1"
-#define NFCTAGTYPE2 "org.nfcforum.ndef.type2"
-#define NFCTAGTYPE3 "org.nfcforum.ndef.type3"
-#define NFCTAGTYPE4 "org.nfcforum.ndef.type4"
+#define NDEFTECHNOLOGY QStringLiteral("android.nfc.tech.Ndef")
+#define NDEFFORMATABLETECHNOLOGY QStringLiteral("android.nfc.tech.NdefFormatable")
+#define ISODEPTECHNOLOGY QStringLiteral("android.nfc.tech.IsoDep")
+#define NFCATECHNOLOGY QStringLiteral("android.nfc.tech.NfcA")
+#define NFCBTECHNOLOGY QStringLiteral("android.nfc.tech.NfcB")
+#define NFCFTECHNOLOGY QStringLiteral("android.nfc.tech.NfcF")
+#define NFCVTECHNOLOGY QStringLiteral("android.nfc.tech.NfcV")
+#define MIFARECLASSICTECHNOLOGY QStringLiteral("android.nfc.tech.MifareClassic")
+#define MIFARECULTRALIGHTTECHNOLOGY QStringLiteral("android.nfc.tech.MifareUltralight")
+
+#define MIFARETAG QStringLiteral("com.nxp.ndef.mifareclassic")
+#define NFCTAGTYPE1 QStringLiteral("org.nfcforum.ndef.type1")
+#define NFCTAGTYPE2 QStringLiteral("org.nfcforum.ndef.type2")
+#define NFCTAGTYPE3 QStringLiteral("org.nfcforum.ndef.type3")
+#define NFCTAGTYPE4 QStringLiteral("org.nfcforum.ndef.type4")
NearFieldTarget::NearFieldTarget(QAndroidJniObject intent, const QByteArray uid, QObject *parent) :
QNearFieldTarget(parent),
m_intent(intent),
- m_uid(uid)
+ m_uid(uid),
+ m_keepConnection(false)
{
updateTechList();
updateType();
@@ -84,13 +86,56 @@ QNearFieldTarget::Type NearFieldTarget::type() const
QNearFieldTarget::AccessMethods NearFieldTarget::accessMethods() const
{
- AccessMethods result = NdefAccess;
+ AccessMethods result = UnknownAccess;
+
+ if (m_techList.contains(NDEFTECHNOLOGY)
+ || m_techList.contains(NDEFFORMATABLETECHNOLOGY))
+ result |= NdefAccess;
+
+ if (m_techList.contains(ISODEPTECHNOLOGY)
+ || m_techList.contains(NFCATECHNOLOGY)
+ || m_techList.contains(NFCBTECHNOLOGY)
+ || m_techList.contains(NFCFTECHNOLOGY)
+ || m_techList.contains(NFCVTECHNOLOGY))
+ result |= TagTypeSpecificAccess;
+
return result;
}
+bool NearFieldTarget::keepConnection() const
+{
+ return m_keepConnection;
+}
+
+bool NearFieldTarget::setKeepConnection(bool isPersistent)
+{
+ m_keepConnection = isPersistent;
+
+ if (!m_keepConnection)
+ disconnect();
+
+ return true;
+}
+
+bool NearFieldTarget::disconnect()
+{
+ if (!m_tagTech.isValid())
+ return false;
+
+ bool connected = m_tagTech.callMethod<jboolean>("isConnected");
+ if (catchJavaExceptions())
+ return false;
+
+ if (!connected)
+ return false;
+
+ m_tagTech.callMethod<void>("close");
+ return !catchJavaExceptions();
+}
+
bool NearFieldTarget::hasNdefMessage()
{
- return m_techList.contains(QStringLiteral(NDEFTECHNOLOGY));
+ return m_techList.contains(NDEFTECHNOLOGY);
}
QNearFieldTarget::RequestId NearFieldTarget::readNdefMessages()
@@ -109,8 +154,7 @@ QNearFieldTarget::RequestId NearFieldTarget::readNdefMessages()
}
// Getting Ndef technology object
- QAndroidJniObject ndef = getTagTechnology(QStringLiteral(NDEFTECHNOLOGY));
- if (!ndef.isValid()) {
+ if (!setTagTechnology({NDEFTECHNOLOGY})) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QNearFieldTarget::Error, QNearFieldTarget::UnsupportedError),
Q_ARG(const QNearFieldTarget::RequestId&, requestId));
@@ -118,8 +162,7 @@ QNearFieldTarget::RequestId NearFieldTarget::readNdefMessages()
}
// Connect
- ndef.callMethod<void>("connect");
- if (catchJavaExceptions()) {
+ if (!connect()) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QNearFieldTarget::Error, QNearFieldTarget::TargetOutOfRangeError),
Q_ARG(const QNearFieldTarget::RequestId&, requestId));
@@ -127,7 +170,7 @@ QNearFieldTarget::RequestId NearFieldTarget::readNdefMessages()
}
// Get NdefMessage object
- QAndroidJniObject ndefMessage = ndef.callObjectMethod("getNdefMessage", "()Landroid/nfc/NdefMessage;");
+ QAndroidJniObject ndefMessage = m_tagTech.callObjectMethod("getNdefMessage", "()Landroid/nfc/NdefMessage;");
if (catchJavaExceptions())
ndefMessage = QAndroidJniObject();
if (!ndefMessage.isValid()) {
@@ -141,9 +184,10 @@ QNearFieldTarget::RequestId NearFieldTarget::readNdefMessages()
QAndroidJniObject ndefMessageBA = ndefMessage.callObjectMethod("toByteArray", "()[B");
QByteArray ndefMessageQBA = jbyteArrayToQByteArray(ndefMessageBA.object<jbyteArray>());
- // Closing connection
- ndef.callMethod<void>("close");
- catchJavaExceptions(); // IOException at this point does not matter anymore.
+ if (!m_keepConnection) {
+ // Closing connection
+ disconnect(); // IOException at this point does not matter anymore.
+ }
// Sending QNdefMessage, requestCompleted and exit.
QNdefMessage qNdefMessage = QNdefMessage::fromByteArray(ndefMessageQBA);
@@ -157,68 +201,89 @@ QNearFieldTarget::RequestId NearFieldTarget::readNdefMessages()
return requestId;
}
+int NearFieldTarget::maxCommandLength() const
+{
+ QAndroidJniObject tagTech;
+ if (m_techList.contains(ISODEPTECHNOLOGY))
+ tagTech = getTagTechnology(ISODEPTECHNOLOGY);
+ else if (m_techList.contains(NFCATECHNOLOGY))
+ tagTech = getTagTechnology(NFCATECHNOLOGY);
+ else if (m_techList.contains(NFCBTECHNOLOGY))
+ tagTech = getTagTechnology(NFCBTECHNOLOGY);
+ else if (m_techList.contains(NFCFTECHNOLOGY))
+ tagTech = getTagTechnology(NFCFTECHNOLOGY);
+ else if (m_techList.contains(NFCVTECHNOLOGY))
+ tagTech = getTagTechnology(NFCVTECHNOLOGY);
+ else
+ return 0;
+
+ int returnVal = tagTech.callMethod<jint>("getMaxTransceiveLength");
+ if (catchJavaExceptions())
+ return 0;
+
+ return returnVal;
+}
QNearFieldTarget::RequestId NearFieldTarget::sendCommand(const QByteArray &command)
{
- Q_UNUSED(command);
- Q_EMIT QNearFieldTarget::error(QNearFieldTarget::UnsupportedError, QNearFieldTarget::RequestId());
- return QNearFieldTarget::RequestId();
-
- //Not supported for now
- /*if (command.size() == 0) {
+ if (command.size() == 0 || command.size() > maxCommandLength()) {
Q_EMIT QNearFieldTarget::error(QNearFieldTarget::InvalidParametersError, QNearFieldTarget::RequestId());
return QNearFieldTarget::RequestId();
}
- AndroidNfc::AttachedJNIEnv aenv;
- JNIEnv *env = aenv.jniEnv;
-
- jobject tagTech;
- if (m_techList.contains(QStringLiteral(NFCATECHNOLOGY))) {
- tagTech = getTagTechnology(QStringLiteral(NFCATECHNOLOGY));
- } else if (m_techList.contains(QStringLiteral(NFCBTECHNOLOGY))) {
- tagTech = getTagTechnology(QStringLiteral(NFCBTECHNOLOGY));
- } else if (m_techList.contains(QStringLiteral(NFCFTECHNOLOGY))) {
- tagTech = getTagTechnology(QStringLiteral(NFCFTECHNOLOGY));
- } else if (m_techList.contains(QStringLiteral(NFCVTECHNOLOGY))) {
- tagTech = getTagTechnology(QStringLiteral(NFCVTECHNOLOGY));
- } else {
+ // Making sure that target has commands
+ if (!(accessMethods() & TagTypeSpecificAccess))
+ return QNearFieldTarget::RequestId();
+
+ QAndroidJniEnvironment env;
+
+ if (!setTagTechnology({ISODEPTECHNOLOGY, NFCATECHNOLOGY, NFCBTECHNOLOGY, NFCFTECHNOLOGY, NFCVTECHNOLOGY})) {
Q_EMIT QNearFieldTarget::error(QNearFieldTarget::UnsupportedError, QNearFieldTarget::RequestId());
return QNearFieldTarget::RequestId();
}
- QByteArray ba(ba);
-
- jclass techClass = env->GetObjectClass(tagTech);
- jmethodID tranceiveMID = env->GetMethodID(techClass, "tranceive", "([B)[B");
- Q_ASSERT_X(tranceiveMID != 0, "sendCommand", "could not find tranceive method");
+ // Connecting
+ QNearFieldTarget::RequestId requestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate());
+ if (!connect()) {
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNearFieldTarget::Error, QNearFieldTarget::TargetOutOfRangeError),
+ Q_ARG(const QNearFieldTarget::RequestId&, requestId));
+ return requestId;
+ }
+ // Making QByteArray
+ QByteArray ba(command);
jbyteArray jba = env->NewByteArray(ba.size());
env->SetByteArrayRegion(jba, 0, ba.size(), reinterpret_cast<jbyte*>(ba.data()));
- jbyteArray rsp = reinterpret_cast<jbyteArray>(env->CallObjectMethod(tagTech, tranceiveMID, jba));
-
- jsize len = env->GetArrayLength(rsp);
- QByteArray rspQBA;
- rspQBA.resize(len);
-
- env->GetByteArrayRegion(rsp, 0, len, reinterpret_cast<jbyte*>(rspQBA.data()));
-
- qDebug() << "Send command returned QBA size: " << rspQBA.size();
-
-
+ // Writing
+ QAndroidJniObject myNewVal = m_tagTech.callObjectMethod("transceive", "([B)[B", jba);
+ if (catchJavaExceptions()) {
+ QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
+ Q_ARG(QNearFieldTarget::Error, QNearFieldTarget::CommandError),
+ Q_ARG(const QNearFieldTarget::RequestId&, requestId));
+ return requestId;
+ }
+ QByteArray result = jbyteArrayToQByteArray(myNewVal.object<jbyteArray>());
env->DeleteLocalRef(jba);
+ handleResponse(requestId, result);
+
+ if (!m_keepConnection) {
+ // Closing connection
+ disconnect(); // IOException at this point does not matter anymore.
+ }
+ QMetaObject::invokeMethod(this, "requestCompleted", Qt::QueuedConnection,
+ Q_ARG(const QNearFieldTarget::RequestId&, requestId));
- return QNearFieldTarget::RequestId();*/
+ return requestId;
}
QNearFieldTarget::RequestId NearFieldTarget::sendCommands(const QList<QByteArray> &commands)
{
QNearFieldTarget::RequestId requestId;
- for (int i=0; i < commands.size(); i++){
+ for (int i=0; i < commands.size(); i++)
requestId = sendCommand(commands.at(i));
- }
return requestId;
}
@@ -234,22 +299,18 @@ QNearFieldTarget::RequestId NearFieldTarget::writeNdefMessages(const QList<QNdef
const char *writeMethod;
QAndroidJniObject tagTechnology;
+ if (!setTagTechnology({NDEFFORMATABLETECHNOLOGY, NDEFTECHNOLOGY}))
+ return QNearFieldTarget::RequestId();
+
// Getting write method
- if (m_techList.contains(QStringLiteral(NDEFFORMATABLETECHNOLOGY))) {
- tagTechnology = getTagTechnology(QStringLiteral(NDEFFORMATABLETECHNOLOGY));
+ if (m_tech == NDEFFORMATABLETECHNOLOGY)
writeMethod = "format";
- } else if (m_techList.contains(QStringLiteral(NDEFTECHNOLOGY))) {
- tagTechnology = getTagTechnology(QStringLiteral(NDEFTECHNOLOGY));
+ else
writeMethod = "writeNdefMessage";
- } else {
- // An invalid request id will be returned if the target does not support writing NDEF messages.
- return QNearFieldTarget::RequestId();
- }
// Connecting
QNearFieldTarget::RequestId requestId = QNearFieldTarget::RequestId(new QNearFieldTarget::RequestIdPrivate());
- tagTechnology.callMethod<void>("connect");
- if (catchJavaExceptions()) {
+ if (!connect()) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QNearFieldTarget::Error, QNearFieldTarget::TargetOutOfRangeError),
Q_ARG(const QNearFieldTarget::RequestId&, requestId));
@@ -278,9 +339,8 @@ QNearFieldTarget::RequestId NearFieldTarget::writeNdefMessages(const QList<QNdef
return requestId;
}
- // Closing connection, sending signal and exit
- tagTechnology.callMethod<void>("close");
- catchJavaExceptions(); // IOException at this point does not matter anymore.
+ if (!m_keepConnection)
+ disconnect(); // IOException at this point does not matter anymore.
QMetaObject::invokeMethod(this, "ndefMessagesWritten", Qt::QueuedConnection);
return requestId;
}
@@ -302,19 +362,26 @@ void NearFieldTarget::setIntent(QAndroidJniObject intent)
void NearFieldTarget::checkIsTargetLost()
{
- if (!m_intent.isValid() || m_techList.isEmpty()) {
+ if (!m_intent.isValid() || !setTagTechnology(m_techList)) {
handleTargetLost();
return;
}
- // Using first available technology to check connection
- QString techStr = m_techList.first();
- QAndroidJniObject tagTech = getTagTechnology(techStr);
- tagTech.callMethod<void>("connect");
+
+ bool connected = m_tagTech.callMethod<jboolean>("isConnected");
+ if (catchJavaExceptions()) {
+ handleTargetLost();
+ return;
+ }
+
+ if (connected)
+ return;
+
+ m_tagTech.callMethod<void>("connect");
if (catchJavaExceptions(false)) {
handleTargetLost();
return;
}
- tagTech.callMethod<void>("close");
+ m_tagTech.callMethod<void>("close");
if (catchJavaExceptions(false))
handleTargetLost();
}
@@ -358,28 +425,28 @@ QNearFieldTarget::Type NearFieldTarget::getTagType() const
{
QAndroidJniEnvironment env;
- if (m_techList.contains(QStringLiteral(NDEFTECHNOLOGY))) {
- QAndroidJniObject ndef = getTagTechnology(QStringLiteral(NDEFTECHNOLOGY));
+ if (m_techList.contains(NDEFTECHNOLOGY)) {
+ QAndroidJniObject ndef = getTagTechnology(NDEFTECHNOLOGY);
QString qtype = ndef.callObjectMethod("getType", "()Ljava/lang/String;").toString();
- if (qtype.compare(QStringLiteral(MIFARETAG)) == 0)
+ if (qtype.compare(MIFARETAG) == 0)
return MifareTag;
- if (qtype.compare(QStringLiteral(NFCTAGTYPE1)) == 0)
+ if (qtype.compare(NFCTAGTYPE1) == 0)
return NfcTagType1;
- if (qtype.compare(QStringLiteral(NFCTAGTYPE2)) == 0)
+ if (qtype.compare(NFCTAGTYPE2) == 0)
return NfcTagType2;
- if (qtype.compare(QStringLiteral(NFCTAGTYPE3)) == 0)
+ if (qtype.compare(NFCTAGTYPE3) == 0)
return NfcTagType3;
- if (qtype.compare(QStringLiteral(NFCTAGTYPE4)) == 0)
+ if (qtype.compare(NFCTAGTYPE4) == 0)
return NfcTagType4;
return ProprietaryTag;
- } else if (m_techList.contains(QStringLiteral(NFCATECHNOLOGY))) {
- if (m_techList.contains(QStringLiteral(MIFARECLASSICTECHNOLOGY)))
+ } else if (m_techList.contains(NFCATECHNOLOGY)) {
+ if (m_techList.contains(MIFARECLASSICTECHNOLOGY))
return MifareTag;
// Checking ATQA/SENS_RES
// xxx0 0000 xxxx xxxx: Identifies tag Type 1 platform
- QAndroidJniObject nfca = getTagTechnology(QStringLiteral(NFCATECHNOLOGY));
+ QAndroidJniObject nfca = getTagTechnology(NFCATECHNOLOGY);
QAndroidJniObject atqaBA = nfca.callObjectMethod("getAtqa", "()[B");
QByteArray atqaQBA = jbyteArrayToQByteArray(atqaBA.object<jbyteArray>());
if (atqaQBA.isEmpty())
@@ -396,9 +463,9 @@ QNearFieldTarget::Type NearFieldTarget::getTagType() const
else if ((sakS & 0x0064) == 0x0020)
return NfcTagType4;
return ProprietaryTag;
- } else if (m_techList.contains(QStringLiteral(NFCBTECHNOLOGY))) {
+ } else if (m_techList.contains(NFCBTECHNOLOGY)) {
return NfcTagType4;
- } else if (m_techList.contains(QStringLiteral(NFCFTECHNOLOGY))) {
+ } else if (m_techList.contains(NFCFTECHNOLOGY)) {
return NfcTagType3;
}
@@ -409,7 +476,7 @@ void NearFieldTarget::setupTargetCheckTimer()
{
m_targetCheckTimer = new QTimer(this);
m_targetCheckTimer->setInterval(1000);
- connect(m_targetCheckTimer, SIGNAL(timeout()), this, SLOT(checkIsTargetLost()));
+ QObject::connect(m_targetCheckTimer, &QTimer::timeout, this, &NearFieldTarget::checkIsTargetLost);
m_targetCheckTimer->start();
}
@@ -427,9 +494,42 @@ QAndroidJniObject NearFieldTarget::getTagTechnology(const QString &tech) const
// Getting requested technology
QAndroidJniObject tag = AndroidNfc::getTag(m_intent);
const QString sig = QString::fromUtf8("(Landroid/nfc/Tag;)L%1;");
- QAndroidJniObject tagtech = QAndroidJniObject::callStaticObjectMethod(techClass.toUtf8().constData(), "get",
+ QAndroidJniObject tagTech = QAndroidJniObject::callStaticObjectMethod(techClass.toUtf8().constData(), "get",
sig.arg(techClass).toUtf8().constData(), tag.object<jobject>());
- return tagtech;
+
+ return tagTech;
+}
+
+bool NearFieldTarget::setTagTechnology(const QStringList &techList)
+{
+ for (const QString &tech : techList) {
+ if (m_techList.contains(tech)) {
+ if (m_tech == tech) {
+ return true;
+ }
+ m_tech = tech;
+ m_tagTech = getTagTechnology(tech);
+ return m_tagTech.isValid();
+ }
+ }
+
+ return false;
+}
+
+bool NearFieldTarget::connect()
+{
+ if (!m_tagTech.isValid())
+ return false;
+
+ bool connected = m_tagTech.callMethod<jboolean>("isConnected");
+ if (catchJavaExceptions())
+ return false;
+
+ if (connected)
+ return true;
+
+ m_tagTech.callMethod<void>("connect");
+ return !catchJavaExceptions();
}
QByteArray NearFieldTarget::jbyteArrayToQByteArray(const jbyteArray &byteArray) const
diff --git a/src/nfc/qnearfieldtarget_android_p.cpp b/src/nfc/qnearfieldtarget_android_p.cpp
new file mode 100644
index 00000000..de553ea9
--- /dev/null
+++ b/src/nfc/qnearfieldtarget_android_p.cpp
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Governikus GmbH & Co. KG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtNfc module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+
+#include "qnearfieldtarget_p.h"
+#include "qnearfieldtarget_android_p.h"
+
+QT_BEGIN_NAMESPACE
+
+bool QNearFieldTargetPrivate::keepConnection() const
+{
+ NEARFIELDTARGET_Q();
+ return q->keepConnection();
+}
+
+bool QNearFieldTargetPrivate::setKeepConnection(bool isPersistent)
+{
+ NEARFIELDTARGET_Q();
+ return q->setKeepConnection(isPersistent);
+}
+
+bool QNearFieldTargetPrivate::disconnect()
+{
+ NEARFIELDTARGET_Q();
+ return q->disconnect();
+}
+
+int QNearFieldTargetPrivate::maxCommandLength() const
+{
+ NEARFIELDTARGET_Q();
+ return q->maxCommandLength();
+}
+
+QT_END_NAMESPACE
diff --git a/src/nfc/qnearfieldtarget_android_p.h b/src/nfc/qnearfieldtarget_android_p.h
index 94bb394d..0063e9a5 100644
--- a/src/nfc/qnearfieldtarget_android_p.h
+++ b/src/nfc/qnearfieldtarget_android_p.h
@@ -75,8 +75,12 @@ public:
virtual QByteArray uid() const;
virtual Type type() const;
virtual AccessMethods accessMethods() const;
+ bool keepConnection() const;
+ bool setKeepConnection(bool isPersistent);
+ bool disconnect();
virtual bool hasNdefMessage();
virtual RequestId readNdefMessages();
+ int maxCommandLength() const;
virtual RequestId sendCommand(const QByteArray &command);
virtual RequestId sendCommands(const QList<QByteArray> &commands);
virtual RequestId writeNdefMessages(const QList<QNdefMessage> &messages);
@@ -98,6 +102,8 @@ protected:
void setupTargetCheckTimer();
void handleTargetLost();
QAndroidJniObject getTagTechnology(const QString &tech) const;
+ bool setTagTechnology(const QStringList &techList);
+ bool connect();
QByteArray jbyteArrayToQByteArray(const jbyteArray &byteArray) const;
bool catchJavaExceptions(bool verbose = true) const;
@@ -107,6 +113,9 @@ protected:
QStringList m_techList;
Type m_type;
QTimer *m_targetCheckTimer;
+ QString m_tech;
+ QAndroidJniObject m_tagTech;
+ bool m_keepConnection;
};
QT_END_NAMESPACE
diff --git a/src/nfc/qnearfieldtarget_emulator.cpp b/src/nfc/qnearfieldtarget_emulator.cpp
index 96462b0d..29b1f74d 100644
--- a/src/nfc/qnearfieldtarget_emulator.cpp
+++ b/src/nfc/qnearfieldtarget_emulator.cpp
@@ -40,11 +40,12 @@
#include "qnearfieldtarget_emulator_p.h"
#include "qnearfieldtarget_p.h"
-#include <QtCore/QDirIterator>
-#include <QtCore/QSettings>
-#include <QtCore/QMutex>
+#include <QtCore/QByteArray>
#include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
+#include <QtCore/QDirIterator>
+#include <QtCore/QMutex>
+#include <QtCore/QSettings>
QT_BEGIN_NAMESPACE
@@ -87,7 +88,7 @@ QNearFieldTarget::RequestId TagType1::sendCommand(const QByteArray &command)
return id;
}
- quint16 crc = qNfcChecksum(command.constData(), command.length());
+ quint16 crc = qChecksum(command.constData(), command.length(), Qt::ChecksumItuV41);
QByteArray response = m_tag->processCommand(command + char(crc & 0xff) + char(crc >> 8));
@@ -99,7 +100,7 @@ QNearFieldTarget::RequestId TagType1::sendCommand(const QByteArray &command)
}
// check crc
- if (qNfcChecksum(response.constData(), response.length()) != 0) {
+ if (qChecksum(response.constData(), response.length(), Qt::ChecksumItuV41) != 0) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QNearFieldTarget::Error, ChecksumMismatchError),
Q_ARG(QNearFieldTarget::RequestId, id));
@@ -157,7 +158,7 @@ QNearFieldTarget::RequestId TagType2::sendCommand(const QByteArray &command)
return id;
}
- quint16 crc = qNfcChecksum(command.constData(), command.length());
+ quint16 crc = qChecksum(command.constData(), command.length(), Qt::ChecksumItuV41);
QByteArray response = m_tag->processCommand(command + char(crc & 0xff) + char(crc >> 8));
@@ -166,7 +167,7 @@ QNearFieldTarget::RequestId TagType2::sendCommand(const QByteArray &command)
if (response.length() > 1) {
// check crc
- if (qNfcChecksum(response.constData(), response.length()) != 0) {
+ if (qChecksum(response.constData(), response.length(), Qt::ChecksumItuV41) != 0) {
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
Q_ARG(QNearFieldTarget::Error, ChecksumMismatchError),
Q_ARG(QNearFieldTarget::RequestId, id));
diff --git a/src/nfc/qnearfieldtarget_neard_p.cpp b/src/nfc/qnearfieldtarget_neard_p.cpp
new file mode 100644
index 00000000..411b80d5
--- /dev/null
+++ b/src/nfc/qnearfieldtarget_neard_p.cpp
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Governikus GmbH & Co. K
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtNfc module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+
+#include "qnearfieldtarget.h"
+#include "qnearfieldtarget_p.h"
+
+QT_BEGIN_NAMESPACE
+
+bool QNearFieldTargetPrivate::keepConnection() const
+{
+ return false;
+}
+
+bool QNearFieldTargetPrivate::setKeepConnection(bool isPersistent)
+{
+ return false;
+}
+
+bool QNearFieldTargetPrivate::disconnect()
+{
+ return false;
+}
+
+int QNearFieldTargetPrivate::maxCommandLength() const
+{
+ return 0;
+}
+
+QT_END_NAMESPACE
diff --git a/src/nfc/qnearfieldtarget_p.cpp b/src/nfc/qnearfieldtarget_p.cpp
new file mode 100644
index 00000000..4ed17a15
--- /dev/null
+++ b/src/nfc/qnearfieldtarget_p.cpp
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Governikus GmbH & Co. K
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtNfc module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+
+#include "qnearfieldtarget.h"
+#include "qnearfieldtarget_p.h"
+
+QT_BEGIN_NAMESPACE
+
+bool QNearFieldTargetPrivate::keepConnection() const
+{
+ return false;
+}
+
+bool QNearFieldTargetPrivate::setKeepConnection(bool isPersistent)
+{
+ Q_UNUSED(isPersistent);
+ return false;
+}
+
+bool QNearFieldTargetPrivate::disconnect()
+{
+ return false;
+}
+
+int QNearFieldTargetPrivate::maxCommandLength() const
+{
+ return 0;
+}
+
+QT_END_NAMESPACE
diff --git a/src/nfc/qnearfieldtarget_p.h b/src/nfc/qnearfieldtarget_p.h
index 7a787ace..9cef2f55 100644
--- a/src/nfc/qnearfieldtarget_p.h
+++ b/src/nfc/qnearfieldtarget_p.h
@@ -57,6 +57,9 @@
#include <QtCore/QMap>
#include <QtCore/QSharedData>
+#include <QtCore/QVariant>
+
+#define NEARFIELDTARGET_Q() NearFieldTarget * const q = reinterpret_cast<NearFieldTarget *>(q_ptr)
QT_BEGIN_NAMESPACE
@@ -66,8 +69,18 @@ class QNearFieldTarget::RequestIdPrivate : public QSharedData
class QNearFieldTargetPrivate
{
+ QNearFieldTarget *q_ptr;
+ Q_DECLARE_PUBLIC(QNearFieldTarget)
+
public:
+ QNearFieldTargetPrivate(QNearFieldTarget *q) : q_ptr(q) {}
+
QMap<QNearFieldTarget::RequestId, QVariant> m_decodedResponses;
+
+ bool keepConnection() const;
+ bool setKeepConnection(bool isPersistent);
+ bool disconnect();
+ int maxCommandLength() const;
};
QT_END_NAMESPACE
diff --git a/src/nfc/targetemulator.cpp b/src/nfc/targetemulator.cpp
index ffc2c1b5..80555593 100644
--- a/src/nfc/targetemulator.cpp
+++ b/src/nfc/targetemulator.cpp
@@ -44,9 +44,6 @@
#include <QtCore/QDebug>
-// Implementation of qNfcChecksum
-#include "qnearfieldtarget.h"
-
QT_BEGIN_NAMESPACE
TagBase::TagBase()
@@ -157,7 +154,7 @@ QByteArray NfcTagType1::processCommand(const QByteArray &command)
QByteArray uid = command.mid(3, 4);
// check checksum
- if (qNfcChecksum(command.constData(), command.length()) != 0)
+ if (qChecksum(command.constData(), command.length(), Qt::ChecksumItuV41) != 0)
return QByteArray();
// check UID
@@ -226,7 +223,7 @@ QByteArray NfcTagType1::processCommand(const QByteArray &command)
QByteArray uid = command.mid(10, 4);
// check checksum
- if (qNfcChecksum(command.constData(), command.length()) != 0)
+ if (qChecksum(command.constData(), command.length(), Qt::ChecksumItuV41) != 0)
return QByteArray();
// check UID
@@ -280,7 +277,7 @@ QByteArray NfcTagType1::processCommand(const QByteArray &command)
}
if (!response.isEmpty()) {
- quint16 crc = qNfcChecksum(response.constData(), response.length());
+ quint16 crc = qChecksum(response.constData(), response.length(), Qt::ChecksumItuV41);
response.append(quint8(crc & 0xff));
response.append(quint8(crc >> 8));
}
@@ -324,7 +321,7 @@ QByteArray NfcTagType2::processCommand(const QByteArray &command)
QByteArray response;
// check checksum
- if (qNfcChecksum(command.constData(), command.length()) != 0)
+ if (qChecksum(command.constData(), command.length(), Qt::ChecksumItuV41) != 0)
return QByteArray();
if (expectPacket2) {
@@ -381,7 +378,7 @@ QByteArray NfcTagType2::processCommand(const QByteArray &command)
}
if (!response.isEmpty()) {
- quint16 crc = qNfcChecksum(response.constData(), response.length());
+ quint16 crc = qChecksum(response.constData(), response.length(), Qt::ChecksumItuV41);
response.append(quint8(crc & 0xff));
response.append(quint8(crc >> 8));
}