summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qlowenergycontroller_android.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_android.cpp')
-rw-r--r--src/bluetooth/qlowenergycontroller_android.cpp439
1 files changed, 422 insertions, 17 deletions
diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp
index 134a21b7..f48c0e85 100644
--- a/src/bluetooth/qlowenergycontroller_android.cpp
+++ b/src/bluetooth/qlowenergycontroller_android.cpp
@@ -1,7 +1,7 @@
/****************************************************************************
**
-** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
-** Contact: http://www.qt-project.org/legal
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
@@ -10,9 +10,9 @@
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and Digia. For licensing terms and
-** conditions see http://qt.digia.com/licensing. For further information
-** use the contact form at http://qt.digia.com/contact-us.
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
@@ -23,8 +23,8 @@
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
-** In addition, as a special exception, Digia gives you certain additional
-** rights. These rights are described in the Digia Qt LGPL Exception
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
@@ -68,6 +68,20 @@ void QLowEnergyControllerPrivate::connectToDevice()
this, &QLowEnergyControllerPrivate::connectionUpdated);
connect(hub, &LowEnergyNotificationHub::servicesDiscovered,
this, &QLowEnergyControllerPrivate::servicesDiscovered);
+ connect(hub, &LowEnergyNotificationHub::serviceDetailsDiscoveryFinished,
+ this, &QLowEnergyControllerPrivate::serviceDetailsDiscoveryFinished);
+ connect(hub, &LowEnergyNotificationHub::characteristicRead,
+ this, &QLowEnergyControllerPrivate::characteristicRead);
+ connect(hub, &LowEnergyNotificationHub::descriptorRead,
+ this, &QLowEnergyControllerPrivate::descriptorRead);
+ connect(hub, &LowEnergyNotificationHub::characteristicWritten,
+ this, &QLowEnergyControllerPrivate::characteristicWritten);
+ connect(hub, &LowEnergyNotificationHub::descriptorWritten,
+ this, &QLowEnergyControllerPrivate::descriptorWritten);
+ connect(hub, &LowEnergyNotificationHub::characteristicChanged,
+ this, &QLowEnergyControllerPrivate::characteristicChanged);
+ connect(hub, &LowEnergyNotificationHub::serviceError,
+ this, &QLowEnergyControllerPrivate::serviceError);
}
if (!hub->javaObject().isValid()) {
@@ -87,9 +101,20 @@ void QLowEnergyControllerPrivate::connectToDevice()
void QLowEnergyControllerPrivate::disconnectFromDevice()
{
+ /* Catch an Android timeout bug. If the device is connecting but cannot
+ * physically connect it seems to ignore the disconnect call below.
+ * At least BluetoothGattCallback.onConnectionStateChange never
+ * arrives. The next BluetoothGatt.connect() works just fine though.
+ * */
+
+ QLowEnergyController::ControllerState oldState = state;
setState(QLowEnergyController::ClosingState);
+
if (hub)
hub->javaObject().callMethod<void>("disconnect");
+
+ if (oldState == QLowEnergyController::ConnectingState)
+ setState(QLowEnergyController::UnconnectedState);
}
void QLowEnergyControllerPrivate::discoverServices()
@@ -103,26 +128,167 @@ 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) {
+ 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*/,
- const QLowEnergyHandle /*charHandle*/,
- const QByteArray &/*newValue*/,
- bool /*writeWithResponse*/)
+void QLowEnergyControllerPrivate::writeCharacteristic(
+ const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle,
+ const QByteArray &newValue,
+ bool writeWithResponse)
{
+ //TODO don't ignore WriteWithResponse, right now we assume responses
+ Q_ASSERT(!service.isNull());
+
+ if (!service->characteristicList.contains(charHandle))
+ return;
+
+ QAndroidJniEnvironment env;
+ jbyteArray payload;
+ payload = env->NewByteArray(newValue.size());
+ env->SetByteArrayRegion(payload, 0, newValue.size(),
+ (jbyte *)newValue.constData());
+
+ bool result = false;
+ if (hub) {
+ qCDebug(QT_BT_ANDROID) << "Write characteristic with handle " << charHandle
+ << newValue.toHex() << "(service:" << service->uuid
+ << ", writeWithResponse:" << writeWithResponse << ")";
+ result = hub->javaObject().callMethod<jboolean>("writeCharacteristic", "(I[BI)Z",
+ charHandle, payload,
+ writeWithResponse ? QLowEnergyService::WriteWithResponse : QLowEnergyService::WriteWithoutResponse);
+ }
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ result = false;
+ }
+
+ env->DeleteLocalRef(payload);
+
+ if (!result)
+ service->setError(QLowEnergyService::CharacteristicWriteError);
}
void QLowEnergyControllerPrivate::writeDescriptor(
- const QSharedPointer<QLowEnergyServicePrivate> /*service*/,
+ const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle /*charHandle*/,
- const QLowEnergyHandle /*descriptorHandle*/,
- const QByteArray &/*newValue*/)
+ const QLowEnergyHandle descHandle,
+ const QByteArray &newValue)
{
+ Q_ASSERT(!service.isNull());
+
+ QAndroidJniEnvironment env;
+ jbyteArray payload;
+ payload = env->NewByteArray(newValue.size());
+ env->SetByteArrayRegion(payload, 0, newValue.size(),
+ (jbyte *)newValue.constData());
+ bool result = false;
+ if (hub) {
+ qCDebug(QT_BT_ANDROID) << "Write descriptor with handle " << descHandle
+ << newValue.toHex() << "(service:" << service->uuid << ")";
+ result = hub->javaObject().callMethod<jboolean>("writeDescriptor", "(I[B)Z",
+ descHandle, payload);
+ }
+
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ result = false;
+ }
+
+ env->DeleteLocalRef(payload);
+
+ if (!result)
+ service->setError(QLowEnergyService::DescriptorWriteError);
+}
+
+void QLowEnergyControllerPrivate::readCharacteristic(
+ const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle charHandle)
+{
+ Q_ASSERT(!service.isNull());
+
+ if (!service->characteristicList.contains(charHandle))
+ return;
+
+ QAndroidJniEnvironment env;
+ bool result = false;
+ if (hub) {
+ qCDebug(QT_BT_ANDROID) << "Read characteristic with handle"
+ << charHandle << service->uuid;
+ result = hub->javaObject().callMethod<jboolean>("readCharacteristic",
+ "(I)Z", charHandle);
+ }
+
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ result = false;
+ }
+
+ if (!result)
+ service->setError(QLowEnergyService::CharacteristicWriteError);
+}
+
+void QLowEnergyControllerPrivate::readDescriptor(
+ const QSharedPointer<QLowEnergyServicePrivate> service,
+ const QLowEnergyHandle /*charHandle*/,
+ const QLowEnergyHandle descriptorHandle)
+{
+ Q_ASSERT(!service.isNull());
+
+ QAndroidJniEnvironment env;
+ bool result = false;
+ if (hub) {
+ qCDebug(QT_BT_ANDROID) << "Read descriptor with handle"
+ << descriptorHandle << service->uuid;
+ result = hub->javaObject().callMethod<jboolean>("readDescriptor",
+ "(I)Z", descriptorHandle);
+ }
+
+ if (env->ExceptionOccurred()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ result = false;
+ }
+
+ if (!result)
+ service->setError(QLowEnergyService::DescriptorWriteError);
}
void QLowEnergyControllerPrivate::connectionUpdated(
@@ -132,12 +298,25 @@ void QLowEnergyControllerPrivate::connectionUpdated(
Q_Q(QLowEnergyController);
const QLowEnergyController::ControllerState oldState = state;
- qCDebug(QT_BT_ANDROID) << "Connection updated" << errorCode << oldState << newState;
+ qCDebug(QT_BT_ANDROID) << "Connection updated:"
+ << "error:" << errorCode
+ << "oldState:" << oldState
+ << "newState:" << newState;
if (errorCode != QLowEnergyController::NoError) {
// ConnectionError if transition from Connecting to Connected
- if (oldState == QLowEnergyController::ConnectingState)
+ if (oldState == QLowEnergyController::ConnectingState) {
setError(QLowEnergyController::ConnectionError);
+ /* There is a bug in Android, when connecting to an unconnectable
+ * device. The connection times out and Android sends error code
+ * 133 (doesn't exist) and STATE_CONNECTED. A subsequent disconnect()
+ * call never sends a STATE_DISCONNECTED either.
+ * As workaround we will trigger disconnect when we encounter
+ * error during connect attempt. This leaves the controller
+ * in a cleaner state.
+ * */
+ newState = QLowEnergyController::UnconnectedState;
+ }
else
setError(errorCode);
}
@@ -162,6 +341,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 +363,219 @@ void QLowEnergyControllerPrivate::servicesDiscovered(
}
}
+void QLowEnergyControllerPrivate::serviceDetailsDiscoveryFinished(
+ const QString &serviceUuid, int startHandle, int endHandle)
+{
+ const QBluetoothUuid service(serviceUuid);
+ if (!serviceList.contains(service)) {
+ qCWarning(QT_BT_ANDROID) << "Discovery done of unknown service:"
+ << service.toString();
+ return;
+ }
+
+ //update service data
+ QSharedPointer<QLowEnergyServicePrivate> pointer =
+ serviceList.value(service);
+ pointer->startHandle = startHandle;
+ pointer->endHandle = endHandle;
+
+ if (hub && hub->javaObject().isValid()) {
+ QAndroidJniObject uuid = QAndroidJniObject::fromString(serviceUuid);
+ QAndroidJniObject javaIncludes = hub->javaObject().callObjectMethod(
+ "includedServices",
+ "(Ljava/lang/String;)Ljava/lang/String;",
+ uuid.object<jstring>());
+ if (javaIncludes.isValid()) {
+ const QStringList list = javaIncludes.toString()
+ .split(QStringLiteral(" "),
+ QString::SkipEmptyParts);
+ foreach (const QString &entry, list) {
+ const QBluetoothUuid service(entry);
+ if (service.isNull())
+ return;
+
+ pointer->includedServices.append(service);
+
+ // update the type of the included service
+ QSharedPointer<QLowEnergyServicePrivate> otherService =
+ serviceList.value(service);
+ if (!otherService.isNull())
+ otherService->type |= QLowEnergyService::IncludedService;
+ }
+ }
+ }
+
+ qCDebug(QT_BT_ANDROID) << "Service" << serviceUuid << "discovered (start:"
+ << startHandle << "end:" << endHandle << ")" << pointer.data();
+
+ pointer->setState(QLowEnergyService::ServiceDiscovered);
+}
+
+void QLowEnergyControllerPrivate::characteristicRead(
+ const QBluetoothUuid &serviceUuid, int handle,
+ const QBluetoothUuid &charUuid, int properties, const QByteArray &data)
+{
+ if (!serviceList.contains(serviceUuid))
+ return;
+
+ QSharedPointer<QLowEnergyServicePrivate> service =
+ serviceList.value(serviceUuid);
+ QLowEnergyHandle charHandle = handle;
+
+ QLowEnergyServicePrivate::CharData &charDetails =
+ service->characteristicList[charHandle];
+
+ //Android uses same property value as Qt which is the Bluetooth LE standard
+ charDetails.properties = QLowEnergyCharacteristic::PropertyType(properties);
+ charDetails.uuid = charUuid;
+ charDetails.value = data;
+ //value handle always one larger than characteristics value handle
+ charDetails.valueHandle = charHandle + 1;
+
+ if (service->state == QLowEnergyService::ServiceDiscovered) {
+ QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
+ if (!characteristic.isValid()) {
+ qCWarning(QT_BT_ANDROID) << "characteristicRead: Cannot find characteristic";
+ return;
+ }
+ emit service->characteristicRead(characteristic, data);
+ }
+}
+
+void QLowEnergyControllerPrivate::descriptorRead(
+ const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid,
+ int descHandle, const QBluetoothUuid &descUuid, const QByteArray &data)
+{
+ if (!serviceList.contains(serviceUuid))
+ return;
+
+ QSharedPointer<QLowEnergyServicePrivate> service =
+ serviceList.value(serviceUuid);
+
+ bool entryUpdated = false;
+ foreach (QLowEnergyHandle charHandle, service->characteristicList.keys()) {
+ QLowEnergyServicePrivate::CharData &charDetails =
+ service->characteristicList[charHandle];
+ if (charDetails.uuid != charUuid)
+ continue;
+
+ // new entry created if it doesn't exist
+ QLowEnergyServicePrivate::DescData &descDetails =
+ charDetails.descriptorList[descHandle];
+ descDetails.uuid = descUuid;
+ descDetails.value = data;
+ entryUpdated = true;
+ break;
+ }
+
+ if (!entryUpdated) {
+ qCWarning(QT_BT_ANDROID) << "Cannot find/update descriptor"
+ << descUuid << charUuid << serviceUuid;
+ } else if (service->state == QLowEnergyService::ServiceDiscovered){
+ QLowEnergyDescriptor descriptor = descriptorForHandle(descHandle);
+ if (!descriptor.isValid()) {
+ qCWarning(QT_BT_ANDROID) << "descriptorRead: Cannot find descriptor";
+ return;
+ }
+ emit service->descriptorRead(descriptor, data);
+ }
+}
+
+void QLowEnergyControllerPrivate::characteristicWritten(
+ int charHandle, const QByteArray &data, QLowEnergyService::ServiceError errorCode)
+{
+ QSharedPointer<QLowEnergyServicePrivate> service =
+ serviceForHandle(charHandle);
+ if (service.isNull())
+ return;
+
+ qCDebug(QT_BT_ANDROID) << "Characteristic write confirmation" << service->uuid
+ << charHandle << data.toHex() << errorCode;
+
+ if (errorCode != QLowEnergyService::NoError) {
+ service->setError(errorCode);
+ return;
+ }
+
+ QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
+ if (!characteristic.isValid()) {
+ qCWarning(QT_BT_ANDROID) << "characteristicWritten: Cannot find characteristic";
+ return;
+ }
+
+ // only update cache when property is readable. Otherwise it remains
+ // empty.
+ if (characteristic.properties() & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(charHandle, data, false);
+ emit service->characteristicWritten(characteristic, data);
+}
+
+void QLowEnergyControllerPrivate::descriptorWritten(
+ int descHandle, const QByteArray &data, QLowEnergyService::ServiceError errorCode)
+{
+ QSharedPointer<QLowEnergyServicePrivate> service =
+ serviceForHandle(descHandle);
+ if (service.isNull())
+ return;
+
+ qCDebug(QT_BT_ANDROID) << "Descriptor write confirmation" << service->uuid
+ << descHandle << data.toHex() << errorCode;
+
+ if (errorCode != QLowEnergyService::NoError) {
+ service->setError(errorCode);
+ return;
+ }
+
+ QLowEnergyDescriptor descriptor = descriptorForHandle(descHandle);
+ if (!descriptor.isValid()) {
+ qCWarning(QT_BT_ANDROID) << "descriptorWritten: Cannot find descriptor";
+ return;
+ }
+
+ updateValueOfDescriptor(descriptor.characteristicHandle(),
+ descHandle, data, false);
+ emit service->descriptorWritten(descriptor, data);
+}
+
+void QLowEnergyControllerPrivate::characteristicChanged(
+ int charHandle, const QByteArray &data)
+{
+ QSharedPointer<QLowEnergyServicePrivate> service =
+ serviceForHandle(charHandle);
+ if (service.isNull())
+ return;
+
+ qCDebug(QT_BT_ANDROID) << "Characteristic change notification" << service->uuid
+ << charHandle << data.toHex();
+
+ QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
+ if (!characteristic.isValid()) {
+ qCWarning(QT_BT_ANDROID) << "characteristicChanged: Cannot find characteristic";
+ return;
+ }
+
+ // only update cache when property is readable. Otherwise it remains
+ // empty.
+ if (characteristic.properties() & QLowEnergyCharacteristic::Read)
+ updateValueOfCharacteristic(characteristic.attributeHandle(),
+ data, false);
+ emit service->characteristicChanged(characteristic, data);
+}
+
+void QLowEnergyControllerPrivate::serviceError(
+ int attributeHandle, QLowEnergyService::ServiceError errorCode)
+{
+ // ignore call if it isn't really an error
+ if (errorCode == QLowEnergyService::NoError)
+ return;
+
+ QSharedPointer<QLowEnergyServicePrivate> service =
+ serviceForHandle(attributeHandle);
+ Q_ASSERT(!service.isNull());
+
+ // ATM we don't really use attributeHandle but later on we might
+ // want to associate the error code with a char or desc
+ service->setError(errorCode);
+}
+
QT_END_NAMESPACE