diff options
Diffstat (limited to 'src/bluetooth/qlowenergycontroller_android.cpp')
-rw-r--r-- | src/bluetooth/qlowenergycontroller_android.cpp | 202 |
1 files changed, 194 insertions, 8 deletions
diff --git a/src/bluetooth/qlowenergycontroller_android.cpp b/src/bluetooth/qlowenergycontroller_android.cpp index cb49188c..20b6519c 100644 --- a/src/bluetooth/qlowenergycontroller_android.cpp +++ b/src/bluetooth/qlowenergycontroller_android.cpp @@ -40,6 +40,12 @@ #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> + QT_BEGIN_NAMESPACE @@ -627,14 +633,113 @@ void QLowEnergyControllerPrivate::serviceError( service->setError(errorCode); } +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 ¶ms, const QLowEnergyAdvertisingData &advertisingData, const QLowEnergyAdvertisingData &scanResponseData) { - Q_UNUSED(params); - Q_UNUSED(advertisingData); - Q_UNUSED(scanResponseData); - setState(QLowEnergyController::AdvertisingState); if (!hub->javaObject().isValid()) { @@ -644,7 +749,14 @@ void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingPa return; } - const bool result = hub->javaObject().callMethod<jboolean>("startAdvertising"); + // 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); @@ -663,11 +775,85 @@ void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnec qCWarning(QT_BT_ANDROID) << "Connection update not implemented for Android"; } -void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &service, +// 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 &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) { + // TODO set all characteristic properties + int permissions = 0; + + //TODO add chars and their descriptors + QAndroidJniObject javaChar = QAndroidJniObject("android/bluetooth/BluetoothGattCharacteristic", + "(Ljava/util/UUID;II)V", + javaUuidfromQtUuid(charData.uuid()).object(), + int(charData.properties()), permissions); + + 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) + qWarning() << "Cannot setup initial characteristic value for " << charData.uuid(); + + env->DeleteLocalRef(jb); + + // TODO set all descriptors + + success = service->androidService.callMethod<jboolean>( + "addCharacteristic", + "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", javaChar.object()); + if (!success) + qWarning() << "Cannot add characteristic" << charData.uuid() << "to service" + << service->uuid; + } + + hub->javaObject().callMethod<void>("addService", + "(Landroid/bluetooth/BluetoothGattService;)V", + service->androidService.object()); } QT_END_NAMESPACE |