summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@digia.com>2014-11-06 15:46:03 +0100
committerAlex Blasche <alexander.blasche@theqtcompany.com>2014-11-10 08:38:10 +0100
commit9277a04640e488916b79a0dcbf818c7f1aa1510d (patch)
treeb534a9f55da305c50bdda8cb9e29bb6831d324a1 /src
parent311eef287d658a376c80047aef942dfbf43eb889 (diff)
Majority of service detail discovery code on Android
Primarily the change adds the required data structures and interfaces on the Java side. What is missing is the reporting of the discovery details back to Qt. Change-Id: I37f2e17bb0f87b4c526f1b43a933b9b09b22be72 Reviewed-by: Timur Pocheptsov <Timur.Pocheptsov@digia.com> Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
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);
}