summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java254
-rw-r--r--src/bluetooth/android/jni_android.cpp2
-rw-r--r--src/bluetooth/android/lowenergynotificationhub.cpp15
-rw-r--r--src/bluetooth/android/lowenergynotificationhub_p.h3
-rw-r--r--src/bluetooth/qlowenergycontroller_android.cpp61
-rw-r--r--src/bluetooth/qlowenergycontroller_p.h1
-rw-r--r--src/bluetooth/qlowenergyservice.cpp2
-rw-r--r--src/bluetooth/qlowenergyservice.h3
-rw-r--r--src/bluetooth/qlowenergyserviceprivate.cpp3
9 files changed, 339 insertions, 5 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 0fd73613..50e2f293 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
@@ -38,12 +38,17 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Hashtable;
import java.util.List;
+import java.util.UUID;
public class QtBluetoothLE {
private static final String TAG = "QtBluetoothGatt";
@@ -69,6 +74,11 @@ public class QtBluetoothLE {
mRemoteGattAddress = remoteAddress;
}
+
+ /*************************************************************/
+ /* Device scan */
+ /*************************************************************/
+
/*
Returns true, if request was successfully completed
*/
@@ -101,6 +111,10 @@ public class QtBluetoothLE {
public native void leScanResult(long qtObject, BluetoothDevice device, int rssi);
+ /*************************************************************/
+ /* Service Discovery */
+ /*************************************************************/
+
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
@@ -146,10 +160,59 @@ public class QtBluetoothLE {
}
leServicesDiscovered(qtObject, errorCode, builder.toString());
}
+
+ public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattCharacteristic characteristic,
+ int status)
+ {
+ GattEntry entry = entries.get(runningHandle);
+ entry.valueKnown = true;
+ entries.set(runningHandle, entry);
+ performServiceDiscoveryForHandle(runningHandle+1, false);
+ }
+
+ public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattCharacteristic characteristic,
+ int status)
+ {
+ System.out.println("onCharacteristicWrite");
+ }
+
+ public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattCharacteristic characteristic)
+ {
+ System.out.println("onCharacteristicChanged");
+ }
+
+ public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattDescriptor descriptor,
+ int status)
+ {
+ GattEntry entry = entries.get(runningHandle);
+ entry.valueKnown = true;
+ entries.set(runningHandle, entry);
+ performServiceDiscoveryForHandle(runningHandle+1, false);
+ }
+
+ public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
+ android.bluetooth.BluetoothGattDescriptor descriptor,
+ int status)
+ {
+ System.out.println("onDescriptorWrite");
+ }
+ //TODO Requires Android API 21 which is not available on CI yet.
+// public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt gatt,
+// int status) {
+// System.out.println("onReliableWriteCompleted");
+// }
+//
+// public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt,
+// int rssi, int status) {
+// System.out.println("onReadRemoteRssi");
+// }
+
};
- public native void leConnectionStateChange(long qtObject, int wasErrorTransition, int newState);
- public native void leServicesDiscovered(long qtObject, int errorCode, String uuidList);
public boolean connect() {
if (mBluetoothGatt != null)
@@ -181,5 +244,192 @@ public class QtBluetoothLE {
return mBluetoothGatt.discoverServices();
}
+ private enum GattEntryType
+ {
+ Service, Characteristic, CharacteristicValue, Descriptor
+ }
+ private class GattEntry
+ {
+ public GattEntryType type;
+ public boolean valueKnown = false;
+ public BluetoothGattService service = null;
+ public BluetoothGattCharacteristic characteristic = null;
+ public BluetoothGattDescriptor descriptor = null;
+ }
+ Hashtable<UUID, List<Integer>> uuidToEntry = new Hashtable<UUID, List<Integer>>(100);
+ ArrayList<GattEntry> entries = new ArrayList<GattEntry>(100);
+
+ private void populateHandles()
+ {
+ // We introduce the notion of artificial handles. While GATT handles
+ // are not exposed on Android they help to quickly identify GATT attributes
+ // on the C++ side. The Qt Api will not expose the handles
+ GattEntry entry = null;
+ List<BluetoothGattService> services = mBluetoothGatt.getServices();
+ for (BluetoothGattService service: services) {
+ entry = new GattEntry();
+ entry.type = GattEntryType.Service;
+ entry.service = service;
+ entries.add(entry);
+
+ //some devices may have more than one service with the same uuid
+ List<Integer> old = uuidToEntry.get(service.getUuid());
+ if (old == null)
+ old = new ArrayList<Integer>();
+ old.add(entries.size()-1);
+ uuidToEntry.put(service.getUuid(), old);
+
+ List<BluetoothGattCharacteristic> charList = service.getCharacteristics();
+ for (BluetoothGattCharacteristic characteristic: charList) {
+ entry = new GattEntry();
+ entry.type = GattEntryType.Characteristic;
+ entry.characteristic = characteristic;
+ entries.add(entry);
+ //uuidToEntry.put(characteristic.getUuid(), entries.size()-1);
+
+ // this emulates GATT value attributes
+ entry = new GattEntry();
+ entry.type = GattEntryType.CharacteristicValue;
+ entries.add(entry);
+ //uuidToEntry.put(characteristic.getUuid(), entry);
+
+ List<BluetoothGattDescriptor> descList = characteristic.getDescriptors();
+ for (BluetoothGattDescriptor desc: descList) {
+ entry = new GattEntry();
+ entry.type = GattEntryType.Descriptor;
+ entry.descriptor = desc;
+ entries.add(entry);
+ //uuidToEntry.put(desc.getUuid(), entries.size()-1);
+ }
+ }
+ }
+
+ entries.trimToSize();
+ }
+
+ private int currentServiceInDiscovery = -1;
+ private int runningHandle = -1;
+ public synchronized boolean discoverServiceDetails(String serviceUuid)
+ {
+ try {
+ if (mBluetoothGatt == null)
+ return false;
+
+ if (entries.isEmpty())
+ populateHandles();
+
+ GattEntry entry;
+ int serviceHandle;
+ try {
+ UUID service = UUID.fromString(serviceUuid);
+ List<Integer> handles = uuidToEntry.get(service);
+ if (handles == null || handles.isEmpty()) {
+ Log.w(TAG, "Unknown service uuid for current device: " + service.toString());
+ return false;
+ }
+
+ //TODO for now we assume we always want the first service in case of uuid collision
+ serviceHandle = handles.get(0);
+ entry = entries.get(serviceHandle);
+ if (entry == null) {
+ Log.w(TAG, "Service with UUID " + service.toString() + " not found");
+ return false;
+ }
+ } catch (IllegalArgumentException ex) {
+ //invalid UUID string passed
+ Log.w(TAG, "Cannot parse given UUID");
+ return false;
+ }
+
+ if (entry.type != GattEntryType.Service) {
+ Log.w(TAG, "Given UUID is not a service UUID: " + serviceUuid);
+ return false;
+ }
+
+ // current service already under investigation
+ if (currentServiceInDiscovery == serviceHandle)
+ return true;
+
+ if (currentServiceInDiscovery != -1) {
+ Log.w(TAG, "Service discovery already running on another service");
+ return false;
+ }
+
+ if (!entry.valueKnown) {
+ performServiceDiscoveryForHandle(serviceHandle, true);
+ } else {
+ Log.w(TAG, "Service already discovered");
+ }
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ return false;
+ }
+
+ return true;
+ }
+
+ private void finishCurrentServiceDiscovery()
+ {
+ GattEntry discoveredService = entries.get(currentServiceInDiscovery);
+ discoveredService.valueKnown = true;
+ entries.set(currentServiceInDiscovery, discoveredService);
+ runningHandle = -1;
+ currentServiceInDiscovery = -1;
+ leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString());
+ }
+
+ private synchronized void performServiceDiscoveryForHandle(int nextHandle, boolean searchStarted)
+ {
+ try {
+ if (searchStarted) {
+ currentServiceInDiscovery = nextHandle;
+ runningHandle = ++nextHandle;
+ } else {
+ runningHandle = nextHandle;
+ }
+
+ GattEntry entry = null;
+ try {
+ entry = entries.get(nextHandle);
+ } catch (IndexOutOfBoundsException ex) {
+ ex.printStackTrace();
+ Log.w(TAG, "Last entry of last service read");
+ finishCurrentServiceDiscovery();
+ return;
+ }
+
+ boolean result = false;
+ switch (entry.type) {
+ case Characteristic:
+ result = mBluetoothGatt.readCharacteristic(entry.characteristic);
+ if (!result)
+ performServiceDiscoveryForHandle(runningHandle+1, false);
+ break;
+ case CharacteristicValue:
+ // ignore -> nothing to do for this artificial type
+ performServiceDiscoveryForHandle(runningHandle+1, false);
+ break;
+ case Descriptor:
+ result = mBluetoothGatt.readDescriptor(entry.descriptor);
+ if (!result)
+ performServiceDiscoveryForHandle(runningHandle+1, false);
+ break;
+ case Service:
+ finishCurrentServiceDiscovery();
+ break;
+ default:
+ Log.w(TAG, "Invalid GATT attribute type");
+ break;
+ }
+
+ } catch(Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ 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/bluetooth/android/jni_android.cpp b/src/bluetooth/android/jni_android.cpp
index 6a293a4e..5ae18161 100644
--- a/src/bluetooth/android/jni_android.cpp
+++ b/src/bluetooth/android/jni_android.cpp
@@ -203,6 +203,8 @@ static JNINativeMethod methods_le[] = {
(void *) LowEnergyNotificationHub::lowEnergy_connectionChange},
{"leServicesDiscovered", "(JILjava/lang/String;)V",
(void *) LowEnergyNotificationHub::lowEnergy_servicesDiscovered},
+ {"leServiceDetailDiscoveryFinished", "(JLjava/lang/String;)V",
+ (void *) LowEnergyNotificationHub::lowEnergy_serviceDetailsDiscovered},
};
static JNINativeMethod methods_server[] = {
diff --git a/src/bluetooth/android/lowenergynotificationhub.cpp b/src/bluetooth/android/lowenergynotificationhub.cpp
index 56c3eec7..e009e5f7 100644
--- a/src/bluetooth/android/lowenergynotificationhub.cpp
+++ b/src/bluetooth/android/lowenergynotificationhub.cpp
@@ -122,4 +122,19 @@ void LowEnergyNotificationHub::lowEnergy_servicesDiscovered(
Q_ARG(QString, uuids));
}
+void LowEnergyNotificationHub::lowEnergy_serviceDetailsDiscovered(
+ JNIEnv *, jobject, jlong qtObject, jobject uuid)
+{
+ lock.lockForRead();
+ LowEnergyNotificationHub *hub = hubMap()->value(qtObject);
+ lock.unlock();
+ if (!hub)
+ return;
+
+ const QString serviceUuid = QAndroidJniObject(uuid).toString();
+ QMetaObject::invokeMethod(hub, "serviceDetailsDiscoveryFinished",
+ Qt::QueuedConnection,
+ Q_ARG(QString, serviceUuid));
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/android/lowenergynotificationhub_p.h b/src/bluetooth/android/lowenergynotificationhub_p.h
index 0cd3b02d..06c7b3ff 100644
--- a/src/bluetooth/android/lowenergynotificationhub_p.h
+++ b/src/bluetooth/android/lowenergynotificationhub_p.h
@@ -57,6 +57,8 @@ public:
jint errorCode, jint newState);
static void lowEnergy_servicesDiscovered(JNIEnv*, jobject, jlong qtObject,
jint errorCode, jobject uuidList);
+ static void lowEnergy_serviceDetailsDiscovered(JNIEnv *, jobject,
+ jlong qtObject, jobject uuid);
QAndroidJniObject javaObject()
{
@@ -67,6 +69,7 @@ signals:
void connectionUpdated(QLowEnergyController::ControllerState newState,
QLowEnergyController::Error errorCode);
void servicesDiscovered(QLowEnergyController::Error errorCode, const QString &uuids);
+ void serviceDetailsDiscoveryFinished(const QString& serviceUuid);
public slots:
private:
diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp
index 134a21b7..3a7ca98d 100644
--- a/src/bluetooth/qlowenergycontroller_android.cpp
+++ b/src/bluetooth/qlowenergycontroller_android.cpp
@@ -68,6 +68,8 @@ void QLowEnergyControllerPrivate::connectToDevice()
this, &QLowEnergyControllerPrivate::connectionUpdated);
connect(hub, &LowEnergyNotificationHub::servicesDiscovered,
this, &QLowEnergyControllerPrivate::servicesDiscovered);
+ connect(hub, &LowEnergyNotificationHub::serviceDetailsDiscoveryFinished,
+ this, &QLowEnergyControllerPrivate::serviceDetailsDiscoveryFinished);
}
if (!hub->javaObject().isValid()) {
@@ -103,9 +105,39 @@ void QLowEnergyControllerPrivate::discoverServices()
}
}
-void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &/*service*/)
+void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &service)
{
+ if (!serviceList.contains(service)) {
+ qCWarning(QT_BT_ANDROID) << "Discovery of unknown service" << service.toString()
+ << "not possible";
+ return;
+ }
+ if (!hub)
+ return;
+
+ //cut leading { and trailing } {xxx-xxx}
+ QString tempUuid = service.toString();
+ tempUuid.chop(1); //remove trailing '}'
+ tempUuid.remove(0, 1); //remove first '{'
+
+ QAndroidJniEnvironment env;
+ QAndroidJniObject uuid = QAndroidJniObject::fromString(tempUuid);
+ bool result = hub->javaObject().callMethod<jboolean>("discoverServiceDetails",
+ "(Ljava/lang/String;)Z",
+ uuid.object<jstring>());
+ if (!result || true) {
+ QSharedPointer<QLowEnergyServicePrivate> servicePrivate =
+ serviceList.value(service);
+ if (!servicePrivate.isNull()) {
+ servicePrivate->setError(QLowEnergyService::UnknownError);
+ servicePrivate->setState(QLowEnergyService::DiscoveryRequired);
+ }
+ qCWarning(QT_BT_ANDROID) << "Cannot discover details for" << service.toString();
+ return;
+ }
+
+ qCDebug(QT_BT_ANDROID) << "Discovery of" << service << "started";
}
void QLowEnergyControllerPrivate::writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> /*service*/,
@@ -113,7 +145,6 @@ void QLowEnergyControllerPrivate::writeCharacteristic(const QSharedPointer<QLowE
const QByteArray &/*newValue*/,
bool /*writeWithResponse*/)
{
-
}
void QLowEnergyControllerPrivate::writeDescriptor(
@@ -162,6 +193,17 @@ void QLowEnergyControllerPrivate::servicesDiscovered(
//Android delivers all services in one go
const QStringList list = foundServices.split(QStringLiteral(" "), QString::SkipEmptyParts);
foreach (const QString &entry, list) {
+ const QBluetoothUuid service(entry);
+ if (service.isNull())
+ return;
+
+ QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate();
+ priv->uuid = service;
+ priv->setController(this);
+
+ QSharedPointer<QLowEnergyServicePrivate> pointer(priv);
+ serviceList.insert(service, pointer);
+
emit q->serviceDiscovered(QBluetoothUuid(entry));
}
@@ -173,4 +215,19 @@ void QLowEnergyControllerPrivate::servicesDiscovered(
}
}
+void QLowEnergyControllerPrivate::serviceDetailsDiscoveryFinished(
+ const QString &serviceUuid)
+{
+ const QBluetoothUuid service(serviceUuid);
+ if (!serviceList.contains(service)) {
+ qCWarning(QT_BT_ANDROID) << "Discovery done of unknown service:"
+ << service.toString();
+ return;
+ }
+
+ QSharedPointer<QLowEnergyServicePrivate> pointer =
+ serviceList.value(service);
+ pointer->setState(QLowEnergyService::ServiceDiscovered);
+}
+
QT_END_NAMESPACE
diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h
index 5dea046d..7676c19e 100644
--- a/src/bluetooth/qlowenergycontroller_p.h
+++ b/src/bluetooth/qlowenergycontroller_p.h
@@ -179,6 +179,7 @@ private slots:
QLowEnergyController::Error errorCode);
void servicesDiscovered(QLowEnergyController::Error errorCode,
const QString &foundServices);
+ void serviceDetailsDiscoveryFinished(const QString& serviceUuid);
#endif
private:
diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp
index 0ec92d45..0c803144 100644
--- a/src/bluetooth/qlowenergyservice.cpp
+++ b/src/bluetooth/qlowenergyservice.cpp
@@ -169,6 +169,8 @@ QT_BEGIN_NAMESPACE
\value DescriptorWriteError An attempt to write a new value to a descriptor
failed. For example, it might be triggered when attempting
to write to a read-only descriptor.
+ \value UnknownError An unknown error occurred when interacting with the service.
+ This value was introduced by Qt 5.5.
*/
/*!
diff --git a/src/bluetooth/qlowenergyservice.h b/src/bluetooth/qlowenergyservice.h
index 903b64fe..136f85ee 100644
--- a/src/bluetooth/qlowenergyservice.h
+++ b/src/bluetooth/qlowenergyservice.h
@@ -56,7 +56,8 @@ public:
NoError = 0,
OperationError,
CharacteristicWriteError,
- DescriptorWriteError
+ DescriptorWriteError,
+ UnknownError
};
enum ServiceState {
diff --git a/src/bluetooth/qlowenergyserviceprivate.cpp b/src/bluetooth/qlowenergyserviceprivate.cpp
index 43bb3498..7c310123 100644
--- a/src/bluetooth/qlowenergyserviceprivate.cpp
+++ b/src/bluetooth/qlowenergyserviceprivate.cpp
@@ -65,6 +65,9 @@ void QLowEnergyServicePrivate::setError(QLowEnergyService::ServiceError newError
void QLowEnergyServicePrivate::setState(QLowEnergyService::ServiceState newState)
{
+ if (state == newState)
+ return;
+
state = newState;
emit stateChanged(newState);
}