summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@qt.io>2016-10-19 13:58:17 +0200
committerAlex Blasche <alexander.blasche@qt.io>2016-11-11 16:08:02 +0000
commitf945ffc90f8f11d73dc87f2d82bb2d246d446e71 (patch)
tree4d0264e7c4a053a4429dfbc736691be480c59273
parent8dbae51970970ddc43688301036180c1c7814450 (diff)
LE/Android: Pass list of advertised services found in scan record
Currently the advertising packet (scan record) received from BLE devices during scanning on Android devices is ignored. Consequently, the serviceUuids() method of the QBluetoothDeviceInfo class returns an empty list for BLE devices. However, this list provides important information needed by clients to identify whether a given device is of interest. This changeset implements parsing of the scan record received from the Android Bluetooth layer. The Java byte array is passed over JNI to the C++ adapter classes, where it's parsed to find any service UUIDs being advertised by the device. Parsing the scan record ourselves is required for compatibility (Qt uses "old-style" LE scan API from Android api level 18.) All found UUIDs are added to a list which is subsequently passed to the user of the QBluetoothDeviceDiscoveryAgent class via the deviceDiscovered(QBluetoothDeviceInfo&) signal. Note: not all Android devices pass the full advertisement data. The service UUID list may be empty in those cases. [ChangeLog][QtBluetooth][Android] Parse list of advertised services found in LE scan record and pass them to API client via QBluetoothDeviceInfo::serviceUuids() Task-number: QTBUG-56625 Change-Id: I253f1b841c7b15b3bbabc9e478de87c81979815e Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java4
-rw-r--r--src/bluetooth/android/androidbroadcastreceiver_p.h4
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver.cpp84
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver_p.h5
-rw-r--r--src/bluetooth/android/jni_android.cpp8
-rw-r--r--src/bluetooth/android/localdevicebroadcastreceiver_p.h2
-rw-r--r--src/bluetooth/android/servicediscoverybroadcastreceiver_p.h2
7 files changed, 95 insertions, 14 deletions
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 deb70dc0..9fe88e9c 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
@@ -114,11 +114,11 @@ public class QtBluetoothLE {
if (qtObject == 0)
return;
- leScanResult(qtObject, device, rssi);
+ leScanResult(qtObject, device, rssi, scanRecord);
}
};
- public native void leScanResult(long qtObject, BluetoothDevice device, int rssi);
+ public native void leScanResult(long qtObject, BluetoothDevice device, int rssi, byte[] scanRecord);
/*************************************************************/
/* Service Discovery */
diff --git a/src/bluetooth/android/androidbroadcastreceiver_p.h b/src/bluetooth/android/androidbroadcastreceiver_p.h
index 01d57992..ed30acad 100644
--- a/src/bluetooth/android/androidbroadcastreceiver_p.h
+++ b/src/bluetooth/android/androidbroadcastreceiver_p.h
@@ -75,8 +75,8 @@ public:
protected:
friend void QtBroadcastReceiver_jniOnReceive(JNIEnv *, jobject, jlong, jobject, jobject);
virtual void onReceive(JNIEnv *env, jobject context, jobject intent) = 0;
- friend void QtBluetoothLE_leScanResult(JNIEnv *, jobject, jlong, jobject, jint);
- virtual void onReceiveLeScan(JNIEnv *env, jobject jBluetoothDevice, jint rssi) = 0;
+ friend void QtBluetoothLE_leScanResult(JNIEnv *, jobject, jlong, jobject, jint, jbyteArray);
+ virtual void onReceiveLeScan(JNIEnv *env, jobject jBluetoothDevice, jint rssi, jbyteArray scanRecord) = 0;
QAndroidJniObject contextObject;
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
index 51f6736a..c807df7f 100644
--- a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
@@ -39,13 +39,16 @@
****************************************************************************/
#include "android/devicediscoverybroadcastreceiver_p.h"
+#include <QtCore/QtEndian>
#include <QtCore/QLoggingCategory>
#include <QtBluetooth/QBluetoothAddress>
#include <QtBluetooth/QBluetoothDeviceInfo>
+#include <QtBluetooth/QBluetoothUuid>
#include "android/jni_android_p.h"
#include <QtCore/private/qjnihelpers_p.h>
#include <QtCore/QHash>
#include <QtCore/qbitarray.h>
+#include <algorithm>
QT_BEGIN_NAMESPACE
@@ -234,6 +237,27 @@ static const MinorClassJavaToQtMapping minorMappings[] = {
{ Q_NULLPTR, 0 }, // index 64 & separator
};
+/*! Advertising Data Type (AD type) for LE scan records, as defined in Bluetooth CSS v6. */
+enum ADType {
+ ADType16BitUuidIncomplete = 0x02,
+ ADType16BitUuidComplete = 0x03,
+ ADType32BitUuidIncomplete = 0x04,
+ ADType32BitUuidComplete = 0x05,
+ ADType128BitUuidIncomplete = 0x06,
+ ADType128BitUuidComplete = 0x07,
+ // .. more will be added when required
+};
+
+// Endianness conversion for quint128 doesn't (yet) exist in qtendian.h
+template <>
+inline quint128 qbswap<quint128>(const quint128 src)
+{
+ quint128 dst;
+ for (int i = 0; i < 16; i++)
+ dst.data[i] = src.data[15 - i];
+ return dst;
+}
+
QBluetoothDeviceInfo::CoreConfigurations qtBtTypeForJavaBtType(jint javaType)
{
const JCachedBtTypes::iterator it = cachedBtTypes()->find(javaType);
@@ -425,18 +449,18 @@ void DeviceDiscoveryBroadcastReceiver::onReceive(JNIEnv *env, jobject context, j
// Runs in Java thread
void DeviceDiscoveryBroadcastReceiver::onReceiveLeScan(
- JNIEnv *env, jobject jBluetoothDevice, jint rssi)
+ JNIEnv *env, jobject jBluetoothDevice, jint rssi, jbyteArray scanRecord)
{
const QAndroidJniObject bluetoothDevice(jBluetoothDevice);
if (!bluetoothDevice.isValid())
return;
- const QBluetoothDeviceInfo info = retrieveDeviceInfo(env, bluetoothDevice, rssi);
+ const QBluetoothDeviceInfo info = retrieveDeviceInfo(env, bluetoothDevice, rssi, scanRecord);
if (info.isValid())
emit deviceDiscovered(info, true);
}
-QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv *env, const QAndroidJniObject &bluetoothDevice, int rssi)
+QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv *env, const QAndroidJniObject &bluetoothDevice, int rssi, jbyteArray scanRecord)
{
const QString deviceName = bluetoothDevice.callObjectMethod<jstring>("getName").toString();
const QBluetoothAddress deviceAddress(bluetoothDevice.callObjectMethod<jstring>("getAddress").toString());
@@ -484,6 +508,60 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv
QBluetoothDeviceInfo info(deviceAddress, deviceName, classType);
info.setRssi(rssi);
+ if (scanRecord != nullptr) {
+ // Parse scan record
+ jboolean isCopy;
+ const char *scanRecordBuffer = reinterpret_cast<const char *>(env->GetByteArrayElements(scanRecord, &isCopy));
+ const int scanRecordLength = env->GetArrayLength(scanRecord);
+
+ QList<QBluetoothUuid> serviceUuids;
+ int i = 0;
+
+ // Spec 4.2, Vol 3, Part C, Chapter 11
+ while (i < scanRecordLength) {
+ // sizeof(EIR Data) = sizeof(Length) + sizeof(EIR data Type) + sizeof(EIR Data)
+ // Length = sizeof(EIR data Type) + sizeof(EIR Data)
+
+ const int nBytes = scanRecordBuffer[i];
+ if (nBytes == 0)
+ break;
+
+ if ((i + nBytes) >= scanRecordLength)
+ break;
+
+ const int adType = scanRecordBuffer[i+1];
+ const char *dataPtr = &scanRecordBuffer[i+2];
+ QBluetoothUuid foundService;
+
+ switch (adType) {
+ case ADType16BitUuidIncomplete:
+ case ADType16BitUuidComplete:
+ foundService = QBluetoothUuid(qFromLittleEndian<quint16>(dataPtr));
+ break;
+ case ADType32BitUuidIncomplete:
+ case ADType32BitUuidComplete:
+ foundService = QBluetoothUuid(qFromLittleEndian<quint32>(dataPtr));
+ break;
+ case ADType128BitUuidIncomplete:
+ case ADType128BitUuidComplete:
+ foundService =
+ QBluetoothUuid(qToBigEndian<quint128>(qFromLittleEndian<quint128>(dataPtr)));
+ break;
+ default:
+ // no other types supported yet and therefore skipped
+ // https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile
+ break;
+ }
+
+ i += nBytes + 1;
+
+ if (!foundService.isNull() && !serviceUuids.contains(foundService))
+ serviceUuids.append(foundService);
+ }
+
+ info.setServiceUuids(serviceUuids, QBluetoothDeviceInfo::DataIncomplete);
+ }
+
if (QtAndroidPrivate::androidSdkVersion() >= 18) {
jint javaBtType = bluetoothDevice.callMethod<jint>("getType");
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h b/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h
index 835a7654..526c57e2 100644
--- a/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver_p.h
@@ -65,7 +65,8 @@ class DeviceDiscoveryBroadcastReceiver : public AndroidBroadcastReceiver
public:
DeviceDiscoveryBroadcastReceiver(QObject* parent = 0);
virtual void onReceive(JNIEnv *env, jobject context, jobject intent);
- virtual void onReceiveLeScan(JNIEnv *env, jobject jBluetoothDevice, jint rssi);
+ virtual void onReceiveLeScan(JNIEnv *env, jobject jBluetoothDevice, jint rssi,
+ jbyteArray scanRecord);
signals:
void deviceDiscovered(const QBluetoothDeviceInfo &info, bool isLeScanResult);
@@ -73,7 +74,7 @@ signals:
private:
QBluetoothDeviceInfo retrieveDeviceInfo(JNIEnv *env, const QAndroidJniObject& bluetoothDevice,
- int rssi);
+ int rssi, jbyteArray scanRecord = nullptr);
};
QT_END_NAMESPACE
diff --git a/src/bluetooth/android/jni_android.cpp b/src/bluetooth/android/jni_android.cpp
index 95011c6c..ae07608a 100644
--- a/src/bluetooth/android/jni_android.cpp
+++ b/src/bluetooth/android/jni_android.cpp
@@ -190,10 +190,12 @@ static void QtBluetoothInputStreamThread_readyData(JNIEnv */*env*/, jobject /*ja
reinterpret_cast<InputStreamThread*>(qtObject)->javaReadyRead(buffer, bufferLength);
}
-void QtBluetoothLE_leScanResult(JNIEnv *env, jobject, jlong qtObject, jobject bluetoothDevice, jint rssi)
+void QtBluetoothLE_leScanResult(JNIEnv *env, jobject, jlong qtObject, jobject bluetoothDevice,
+ jint rssi, jbyteArray scanRecord)
{
reinterpret_cast<AndroidBroadcastReceiver*>(qtObject)->onReceiveLeScan(
- env, bluetoothDevice, rssi);
+ env, bluetoothDevice, rssi,
+ scanRecord);
}
@@ -203,7 +205,7 @@ static JNINativeMethod methods[] = {
};
static JNINativeMethod methods_le[] = {
- {"leScanResult", "(JLandroid/bluetooth/BluetoothDevice;I)V",
+ {"leScanResult", "(JLandroid/bluetooth/BluetoothDevice;I[B)V",
(void *) QtBluetoothLE_leScanResult},
{"leConnectionStateChange", "(JII)V",
(void *) LowEnergyNotificationHub::lowEnergy_connectionChange},
diff --git a/src/bluetooth/android/localdevicebroadcastreceiver_p.h b/src/bluetooth/android/localdevicebroadcastreceiver_p.h
index 90f40333..261bf29d 100644
--- a/src/bluetooth/android/localdevicebroadcastreceiver_p.h
+++ b/src/bluetooth/android/localdevicebroadcastreceiver_p.h
@@ -64,7 +64,7 @@ public:
explicit LocalDeviceBroadcastReceiver(QObject *parent = 0);
virtual ~LocalDeviceBroadcastReceiver() {}
virtual void onReceive(JNIEnv *env, jobject context, jobject intent);
- virtual void onReceiveLeScan(JNIEnv *, jobject, jint) {}
+ virtual void onReceiveLeScan(JNIEnv *, jobject, jint, jbyteArray) {}
bool pairingConfirmation(bool accept);
signals:
diff --git a/src/bluetooth/android/servicediscoverybroadcastreceiver_p.h b/src/bluetooth/android/servicediscoverybroadcastreceiver_p.h
index f0abf511..a93cbd3f 100644
--- a/src/bluetooth/android/servicediscoverybroadcastreceiver_p.h
+++ b/src/bluetooth/android/servicediscoverybroadcastreceiver_p.h
@@ -66,7 +66,7 @@ class ServiceDiscoveryBroadcastReceiver : public AndroidBroadcastReceiver
public:
ServiceDiscoveryBroadcastReceiver(QObject* parent = 0);
virtual void onReceive(JNIEnv *env, jobject context, jobject intent);
- virtual void onReceiveLeScan(JNIEnv *, jobject, jint) {}
+ virtual void onReceiveLeScan(JNIEnv *, jobject, jint, jbyteArray) {}
static QList<QBluetoothUuid> convertParcelableArray(const QAndroidJniObject &obj);