Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qlowenergycontroller_android_p.h"
#include "android/androidutils_p.h"
#include "android/jni_android_p.h"
#include <QCoreApplication>
#include <QtCore/QLoggingCategory>
#include <QtCore/QJniEnvironment>
#include <QtCore/QJniObject>
#include <QtCore/q26numeric.h>
#include <QtBluetooth/QLowEnergyServiceData>
#include <QtBluetooth/QLowEnergyCharacteristicData>
#include <QtBluetooth/QLowEnergyDescriptorData>
#include <QtBluetooth/QLowEnergyAdvertisingData>
#include <QtBluetooth/QLowEnergyAdvertisingParameters>
#include <QtBluetooth/QLowEnergyConnectionParameters>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
// BT Core v5.3, 3.2.9, Vol 3, Part F
const int BTLE_MAX_ATTRIBUTE_VALUE_SIZE = 512;
// Conversion: QBluetoothUuid -> java.util.UUID
static QJniObject javaUuidfromQtUuid(const QBluetoothUuid& uuid)
{
// cut off leading and trailing brackets
const QString output = uuid.toString(QUuid::WithoutBraces);
QJniObject javaString = QJniObject::fromString(output);
QJniObject javaUuid = QJniObject::callStaticMethod<QtJniTypes::UUID>(
QtJniTypes::Traits<QtJniTypes::UUID>::className(), "fromString",
javaString.object<jstring>());
return javaUuid;
}
QLowEnergyControllerPrivateAndroid::QLowEnergyControllerPrivateAndroid()
: QLowEnergyControllerPrivate(),
hub(0)
{
registerQLowEnergyControllerMetaType();
}
QLowEnergyControllerPrivateAndroid::~QLowEnergyControllerPrivateAndroid()
{
if (role == QLowEnergyController::PeripheralRole) {
if (hub)
hub->javaObject().callMethod<void>("disconnectServer");
}
}
void QLowEnergyControllerPrivateAndroid::init()
{
const bool isPeripheral = (role == QLowEnergyController::PeripheralRole);
if (isPeripheral) {
qRegisterMetaType<QJniObject>();
hub = new LowEnergyNotificationHub(remoteDevice, isPeripheral, this);
// we only connect to the peripheral role specific signals
// TODO add connections as they get added later on
connect(hub, &LowEnergyNotificationHub::connectionUpdated,
this, &QLowEnergyControllerPrivateAndroid::connectionUpdated);
connect(hub, &LowEnergyNotificationHub::mtuChanged,
this, &QLowEnergyControllerPrivateAndroid::mtuChanged);
connect(hub, &LowEnergyNotificationHub::advertisementError,
this, &QLowEnergyControllerPrivateAndroid::advertisementError);
connect(hub, &LowEnergyNotificationHub::serverCharacteristicChanged,
this, &QLowEnergyControllerPrivateAndroid::serverCharacteristicChanged);
connect(hub, &LowEnergyNotificationHub::serverDescriptorWritten,
this, &QLowEnergyControllerPrivateAndroid::serverDescriptorWritten);
} else {
hub = new LowEnergyNotificationHub(remoteDevice, isPeripheral, this);
// we only connect to the central role specific signals
connect(hub, &LowEnergyNotificationHub::connectionUpdated,
this, &QLowEnergyControllerPrivateAndroid::connectionUpdated);
connect(hub, &LowEnergyNotificationHub::mtuChanged,
this, &QLowEnergyControllerPrivateAndroid::mtuChanged);
connect(hub, &LowEnergyNotificationHub::servicesDiscovered,
this, &QLowEnergyControllerPrivateAndroid::servicesDiscovered);
connect(hub, &LowEnergyNotificationHub::serviceDetailsDiscoveryFinished,
this, &QLowEnergyControllerPrivateAndroid::serviceDetailsDiscoveryFinished);
connect(hub, &LowEnergyNotificationHub::characteristicRead,
this, &QLowEnergyControllerPrivateAndroid::characteristicRead);
connect(hub, &LowEnergyNotificationHub::descriptorRead,
this, &QLowEnergyControllerPrivateAndroid::descriptorRead);
connect(hub, &LowEnergyNotificationHub::characteristicWritten,
this, &QLowEnergyControllerPrivateAndroid::characteristicWritten);
connect(hub, &LowEnergyNotificationHub::descriptorWritten,
this, &QLowEnergyControllerPrivateAndroid::descriptorWritten);
connect(hub, &LowEnergyNotificationHub::characteristicChanged,
this, &QLowEnergyControllerPrivateAndroid::characteristicChanged);
connect(hub, &LowEnergyNotificationHub::serviceError,
this, &QLowEnergyControllerPrivateAndroid::serviceError);
connect(hub, &LowEnergyNotificationHub::remoteRssiRead,
this, &QLowEnergyControllerPrivateAndroid::remoteRssiRead);
}
}
void QLowEnergyControllerPrivateAndroid::connectToDevice()
{
if (!hub) {
qCCritical(QT_BT_ANDROID) << "connectToDevice() LE controller has not been initialized";
return;
}
if (!ensureAndroidPermission(QBluetoothPermission::Access)) {
// This is unlikely to happen as a valid local adapter is a precondition
setError(QLowEnergyController::MissingPermissionsError);
qCWarning(QT_BT_ANDROID) << "connectToDevice() failed due to missing permissions";
return;
}
// required to pass unit test on default backend
if (remoteDevice.isNull()) {
qCWarning(QT_BT_ANDROID) << "Invalid/null remote device address";
setError(QLowEnergyController::UnknownRemoteDeviceError);
return;
}
setState(QLowEnergyController::ConnectingState);
if (!hub->javaObject().isValid()) {
qCWarning(QT_BT_ANDROID) << "Cannot initiate QtBluetoothLE";
setError(QLowEnergyController::ConnectionError);
setState(QLowEnergyController::UnconnectedState);
return;
}
bool result = hub->javaObject().callMethod<jboolean>("connect");
if (!result) {
setError(QLowEnergyController::ConnectionError);
setState(QLowEnergyController::UnconnectedState);
return;
}
}
void QLowEnergyControllerPrivateAndroid::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) {
if (role == QLowEnergyController::PeripheralRole)
hub->javaObject().callMethod<void>("disconnectServer");
else
hub->javaObject().callMethod<void>("disconnect");
}
if (oldState == QLowEnergyController::ConnectingState)
setState(QLowEnergyController::UnconnectedState);
}
void QLowEnergyControllerPrivateAndroid::discoverServices()
{
// No need to check bluetooth permissions here as 'connected' is a precondition
if (hub && hub->javaObject().callMethod<jboolean>("discoverServices")) {
qCDebug(QT_BT_ANDROID) << "Service discovery initiated";
} else {
//revert to connected state
setError(QLowEnergyController::NetworkError);
setState(QLowEnergyController::ConnectedState);
}
}
void QLowEnergyControllerPrivateAndroid::discoverServiceDetails(
const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode)
{
Q_UNUSED(mode);
if (!serviceList.contains(service)) {
qCWarning(QT_BT_ANDROID) << "Discovery of unknown service" << service.toString()
<< "not possible";
return;
}
if (!hub)
return;
QString tempUuid = service.toString(QUuid::WithoutBraces);
QJniEnvironment env;
QJniObject uuid = QJniObject::fromString(tempUuid);
bool readAllValues = mode == QLowEnergyService::FullDiscovery;
bool result = hub->javaObject().callMethod<jboolean>("discoverServiceDetails",
uuid.object<jstring>(),
readAllValues);
if (!result) {
QSharedPointer<QLowEnergyServicePrivate> servicePrivate =
serviceList.value(service);
if (!servicePrivate.isNull()) {
servicePrivate->setError(QLowEnergyService::UnknownError);
servicePrivate->setState(QLowEnergyService::RemoteService);
}
qCWarning(QT_BT_ANDROID) << "Cannot discover details for" << service.toString();
return;
}
qCDebug(QT_BT_ANDROID) << "Discovery of" << service << "started";
}
void QLowEnergyControllerPrivateAndroid::writeCharacteristic(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QByteArray &newValue,
QLowEnergyService::WriteMode mode)
{
//TODO don't ignore WriteWithResponse, right now we assume responses
Q_ASSERT(!service.isNull());
if (!service->characteristicList.contains(charHandle))
return;
QJniEnvironment env;
const jsize nativeSize = q26::saturate_cast<jsize>(newValue.size());
jbyteArray payload;
payload = env->NewByteArray(nativeSize);
env->SetByteArrayRegion(payload, 0, nativeSize,
(jbyte *)newValue.constData());
bool result = false;
if (hub) {
if (role == QLowEnergyController::CentralRole) {
qCDebug(QT_BT_ANDROID) << "Write characteristic with handle " << charHandle
<< newValue.toHex() << "(service:" << service->uuid
<< ", writeWithResponse:" << (mode == QLowEnergyService::WriteWithResponse)
<< ", signed:" << (mode == QLowEnergyService::WriteSigned) << ")";
result = hub->javaObject().callMethod<jboolean>("writeCharacteristic",
charHandle, payload, jint(mode));
} else { // peripheral mode
qCDebug(QT_BT_ANDROID) << "Write server characteristic with handle " << charHandle
<< newValue.toHex() << "(service:" << service->uuid;
const auto &characteristic = characteristicForHandle(charHandle);
if (characteristic.isValid()) {
const QJniObject charUuid = javaUuidfromQtUuid(characteristic.uuid());
result = hub->javaObject().callMethod<jboolean>(
"writeCharacteristic",
service->androidService.object<QtJniTypes::BluetoothGattService>(),
charUuid.object<QtJniTypes::UUID>(), payload);
if (result)
service->characteristicList[charHandle].value = newValue;
}
}
}
env->DeleteLocalRef(payload);
if (!result)
service->setError(QLowEnergyService::CharacteristicWriteError);
}
void QLowEnergyControllerPrivateAndroid::writeDescriptor(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle,
const QLowEnergyHandle descHandle,
const QByteArray &newValue)
{
Q_ASSERT(!service.isNull());
QJniEnvironment env;
const jsize nativeSize = q26::saturate_cast<jsize>(newValue.size());
jbyteArray payload;
payload = env->NewByteArray(nativeSize);
env->SetByteArrayRegion(payload, 0, nativeSize,
(jbyte *)newValue.constData());
bool result = false;
if (hub) {
if (role == QLowEnergyController::CentralRole) {
qCDebug(QT_BT_ANDROID) << "Write descriptor with handle " << descHandle
<< newValue.toHex() << "(service:" << service->uuid << ")";
result = hub->javaObject().callMethod<jboolean>("writeDescriptor",
descHandle, payload);
} else {
const auto &characteristic = characteristicForHandle(charHandle);
const auto &descriptor = descriptorForHandle(descHandle);
if (characteristic.isValid() && descriptor.isValid()) {
qCDebug(QT_BT_ANDROID) << "Write descriptor" << descriptor.uuid()
<< "(service:" << service->uuid
<< "char: " << characteristic.uuid() << ")";
const QJniObject charUuid = javaUuidfromQtUuid(characteristic.uuid());
const QJniObject descUuid = javaUuidfromQtUuid(descriptor.uuid());
result = hub->javaObject().callMethod<jboolean>(
"writeDescriptor",
service->androidService.object<QtJniTypes::BluetoothGattService>(),
charUuid.object<QtJniTypes::UUID>(), descUuid.object<QtJniTypes::UUID>(),
payload);
if (result)
service->characteristicList[charHandle].descriptorList[descHandle].value = newValue;
}
}
}
env->DeleteLocalRef(payload);
if (!result)
service->setError(QLowEnergyService::DescriptorWriteError);
}
void QLowEnergyControllerPrivateAndroid::readCharacteristic(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle charHandle)
{
Q_ASSERT(!service.isNull());
if (!service->characteristicList.contains(charHandle))
return;
QJniEnvironment env;
bool result = false;
if (hub) {
qCDebug(QT_BT_ANDROID) << "Read characteristic with handle"
<< charHandle << service->uuid;
result = hub->javaObject().callMethod<jboolean>("readCharacteristic", charHandle);
}
if (!result)
service->setError(QLowEnergyService::CharacteristicReadError);
}
void QLowEnergyControllerPrivateAndroid::readDescriptor(
const QSharedPointer<QLowEnergyServicePrivate> service,
const QLowEnergyHandle /*charHandle*/,
const QLowEnergyHandle descriptorHandle)
{
Q_ASSERT(!service.isNull());
QJniEnvironment env;
bool result = false;
if (hub) {
qCDebug(QT_BT_ANDROID) << "Read descriptor with handle"
<< descriptorHandle << service->uuid;
result = hub->javaObject().callMethod<jboolean>("readDescriptor", descriptorHandle);
}
if (!result)
service->setError(QLowEnergyService::DescriptorReadError);
}
void QLowEnergyControllerPrivateAndroid::connectionUpdated(
QLowEnergyController::ControllerState newState,
QLowEnergyController::Error errorCode)
{
qCDebug(QT_BT_ANDROID) << "Connection updated:"
<< "error:" << errorCode
<< "oldState:" << state
<< "newState:" << newState;
if (role == QLowEnergyController::PeripheralRole)
peripheralConnectionUpdated(newState, errorCode);
else
centralConnectionUpdated(newState, errorCode);
}
void QLowEnergyControllerPrivateAndroid::mtuChanged(int mtu)
{
Q_Q(QLowEnergyController);
qCDebug(QT_BT_ANDROID) << "MTU updated:"
<< "mtu:" << mtu;
emit q->mtuChanged(mtu);
}
void QLowEnergyControllerPrivateAndroid::remoteRssiRead(int rssi, bool success)
{
Q_Q(QLowEnergyController);
if (success) {
// BT Core v5.3, 7.5.4, Vol 4, Part E
// The LE RSSI can take values -127..127 => narrowing to qint16 is safe
emit q->rssiRead(rssi);
} else {
qCDebug(QT_BT_ANDROID) << "Reading remote RSSI failed";
setError(QLowEnergyController::RssiReadError);
}
}
// called if server/peripheral
void QLowEnergyControllerPrivateAndroid::peripheralConnectionUpdated(
QLowEnergyController::ControllerState newState,
QLowEnergyController::Error errorCode)
{
// Java errorCode can be larger than max QLowEnergyController::Error
if (errorCode > QLowEnergyController::AdvertisingError)
errorCode = QLowEnergyController::UnknownError;
if (errorCode != QLowEnergyController::NoError)
setError(errorCode);
const QLowEnergyController::ControllerState oldState = state;
setState(newState);
// disconnect implies stop of advertisement
if (newState == QLowEnergyController::UnconnectedState)
stopAdvertising();
// The remote name and address may have changed
if (hub) {
remoteDevice = QBluetoothAddress(
hub->javaObject().callMethod<jstring>("remoteAddress").toString());
remoteName = hub->javaObject().callMethod<jstring>("remoteName").toString();
}
Q_Q(QLowEnergyController);
// Emit (dis)connected if the connection state changes
if (oldState == QLowEnergyController::ConnectedState
&& newState != QLowEnergyController::ConnectedState) {
emit q->disconnected();
} else if (newState == QLowEnergyController::ConnectedState
&& oldState != QLowEnergyController::ConnectedState) {
emit q->connected();
}
}
// called if client/central
void QLowEnergyControllerPrivateAndroid::centralConnectionUpdated(
QLowEnergyController::ControllerState newState,
QLowEnergyController::Error errorCode)
{
Q_Q(QLowEnergyController);
const QLowEnergyController::ControllerState oldState = state;
if (errorCode != QLowEnergyController::NoError) {
// ConnectionError if transition from Connecting to Connected
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);
}
setState(newState);
if (newState == QLowEnergyController::UnconnectedState
&& !(oldState == QLowEnergyController::UnconnectedState
|| oldState == QLowEnergyController::ConnectingState)) {
// Invalidate the services if the disconnect came from the remote end.
// Qtherwise we disconnected via QLowEnergyController::disconnectDevice() which
// triggered invalidation already
if (!serviceList.isEmpty()) {
Q_ASSERT(oldState != QLowEnergyController::ClosingState);
invalidateServices();
}
emit q->disconnected();
} else if (newState == QLowEnergyController::ConnectedState
&& oldState != QLowEnergyController::ConnectedState ) {
emit q->connected();
}
}
void QLowEnergyControllerPrivateAndroid::servicesDiscovered(
QLowEnergyController::Error errorCode, const QString &foundServices)
{
Q_Q(QLowEnergyController);
if (errorCode == QLowEnergyController::NoError) {
//Android delivers all services in one go
const QStringList list = foundServices.split(QChar::Space, Qt::SkipEmptyParts);
for (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));
}
setState(QLowEnergyController::DiscoveredState);
emit q->discoveryFinished();
} else {
setError(errorCode);
setState(QLowEnergyController::ConnectedState);
}
}
void QLowEnergyControllerPrivateAndroid::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()) {
QJniObject uuid = QJniObject::fromString(serviceUuid);
QJniObject javaIncludes = hub->javaObject().callMethod<jstring>(
"includedServices", uuid.object<jstring>());
if (javaIncludes.isValid()) {
const QStringList list = javaIncludes.toString()
.split(QChar::Space, Qt::SkipEmptyParts);
for (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::RemoteServiceDiscovered);
}
void QLowEnergyControllerPrivateAndroid::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::RemoteServiceDiscovered) {
QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
if (!characteristic.isValid()) {
qCWarning(QT_BT_ANDROID) << "characteristicRead: Cannot find characteristic";
return;
}
emit service->characteristicRead(characteristic, data);
}
}
void QLowEnergyControllerPrivateAndroid::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;
CharacteristicDataMap::iterator charIt = service->characteristicList.begin();
for ( ; charIt != service->characteristicList.end(); ++charIt) {
QLowEnergyServicePrivate::CharData &charDetails = charIt.value();
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::RemoteServiceDiscovered){
QLowEnergyDescriptor descriptor = descriptorForHandle(descHandle);
if (!descriptor.isValid()) {
qCWarning(QT_BT_ANDROID) << "descriptorRead: Cannot find descriptor";
return;
}
emit service->descriptorRead(descriptor, data);
}
}
void QLowEnergyControllerPrivateAndroid::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 QLowEnergyControllerPrivateAndroid::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 QLowEnergyControllerPrivateAndroid::serverDescriptorWritten(
const QJniObject &jniDesc, const QByteArray &newValue)
{
qCDebug(QT_BT_ANDROID) << "Server descriptor change notification" << newValue.toHex();
// retrieve service, char and desc uuids
const QJniObject jniChar = jniDesc.callMethod<QtJniTypes::BluetoothGattCharacteristic>(
"getCharacteristic");
if (!jniChar.isValid())
return;
const QJniObject jniService =
jniChar.callMethod<QtJniTypes::BluetoothGattService>("getService");
if (!jniService.isValid())
return;
QJniObject jniUuid = jniService.callMethod<QtJniTypes::UUID>("getUuid");
const QBluetoothUuid serviceUuid(jniUuid.toString());
if (serviceUuid.isNull())
return;
// TODO test if two service with same uuid exist
if (!localServices.contains(serviceUuid))
return;
jniUuid = jniChar.callMethod<QtJniTypes::UUID>("getUuid");
const QBluetoothUuid characteristicUuid(jniUuid.toString());
if (characteristicUuid.isNull())
return;
jniUuid = jniDesc.callMethod<QtJniTypes::UUID>("getUuid");
const QBluetoothUuid descriptorUuid(jniUuid.toString());
if (descriptorUuid.isNull())
return;
// find matching QLEDescriptor
auto servicePrivate = localServices.value(serviceUuid);
// TODO test if service contains two characteristics with same uuid
// or characteristic contains two descriptors with same uuid
const auto handleList = servicePrivate->characteristicList.keys();
for (const auto charHandle: handleList) {
const auto &charData = servicePrivate->characteristicList.value(charHandle);
if (charData.uuid != characteristicUuid)
continue;
const auto &descHandleList = charData.descriptorList.keys();
for (const auto descHandle: descHandleList) {
const auto &descData = charData.descriptorList.value(descHandle);
if (descData.uuid != descriptorUuid)
continue;
qCDebug(QT_BT_ANDROID) << "serverDescriptorChanged: Matching descriptor"
<< descriptorUuid << "in char" << characteristicUuid
<< "of service" << serviceUuid;
servicePrivate->characteristicList[charHandle].descriptorList[descHandle].value = newValue;
emit servicePrivate->descriptorWritten(
QLowEnergyDescriptor(servicePrivate, charHandle, descHandle),
newValue);
return;
}
}
}
void QLowEnergyControllerPrivateAndroid::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() << "length:" << data.size();
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 QLowEnergyControllerPrivateAndroid::serverCharacteristicChanged(
const QJniObject &characteristic, const QByteArray &newValue)
{
qCDebug(QT_BT_ANDROID) << "Server characteristic change notification"
<< newValue.toHex() << "length:" << newValue.size();
// match characteristic to servicePrivate
QJniObject service = characteristic.callMethod<QtJniTypes::BluetoothGattService>(
"getService");
if (!service.isValid())
return;
QJniObject jniUuid = service.callMethod<QtJniTypes::UUID>("getUuid");
QBluetoothUuid serviceUuid(jniUuid.toString());
if (serviceUuid.isNull())
return;
// TODO test if two service with same uuid exist
if (!localServices.contains(serviceUuid))
return;
auto servicePrivate = localServices.value(serviceUuid);
jniUuid = characteristic.callMethod<QtJniTypes::UUID>("getUuid");
QBluetoothUuid characteristicUuid(jniUuid.toString());
if (characteristicUuid.isNull())
return;
QLowEnergyHandle foundHandle = 0;
const auto handleList = servicePrivate->characteristicList.keys();
// TODO test if service contains two characteristics with same uuid
for (const auto handle: handleList) {
QLowEnergyServicePrivate::CharData &charData = servicePrivate->characteristicList[handle];
if (charData.uuid != characteristicUuid)
continue;
qCDebug(QT_BT_ANDROID) << "serverCharacteristicChanged: Matching characteristic"
<< characteristicUuid << " on " << serviceUuid;
charData.value = newValue;
foundHandle = handle;
break;
}
if (!foundHandle)
return;
emit servicePrivate->characteristicChanged(
QLowEnergyCharacteristic(servicePrivate, foundHandle), newValue);
}
void QLowEnergyControllerPrivateAndroid::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);
}
void QLowEnergyControllerPrivateAndroid::advertisementError(int errorCode)
{
Q_Q(QLowEnergyController);
switch (errorCode)
{
case 1: // AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE
errorString = QLowEnergyController::tr("Advertisement data is larger than 31 bytes");
break;
case 2: // AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED
errorString = QLowEnergyController::tr("Advertisement feature not supported on the platform");
break;
case 3: // AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR
errorString = QLowEnergyController::tr("Error occurred trying to start advertising");
break;
case 4: // AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS
errorString = QLowEnergyController::tr("Failed due to too many advertisers");
break;
default:
errorString = QLowEnergyController::tr("Unknown advertisement error");
break;
}
error = QLowEnergyController::AdvertisingError;
emit q->errorOccurred(error);
// not relevant states in peripheral mode
Q_ASSERT(state != QLowEnergyController::DiscoveredState);
Q_ASSERT(state != QLowEnergyController::DiscoveringState);
switch (state)
{
case QLowEnergyController::UnconnectedState:
case QLowEnergyController::ConnectingState:
case QLowEnergyController::ConnectedState:
case QLowEnergyController::ClosingState:
// noop as remote is already connected or about to disconnect.
// when connection drops we reset to unconnected anyway
break;
case QLowEnergyController::AdvertisingState:
setState(QLowEnergyController::UnconnectedState);
break;
default:
break;
}
}
static QJniObject javaParcelUuidfromQtUuid(const QBluetoothUuid &uuid)
{
QString output = uuid.toString();
// cut off leading and trailing brackets
output = output.mid(1, output.size()-2);
QJniObject javaString = QJniObject::fromString(output);
QJniObject parcelUuid = QJniObject::callStaticMethod<QtJniTypes::ParcelUuid>(
QtJniTypes::Traits<QtJniTypes::ParcelUuid>::className(), "fromString",
javaString.object<jstring>());
return parcelUuid;
}
static QJniObject createJavaAdvertiseData(const QLowEnergyAdvertisingData &data)
{
QJniObject builder = QJniObject::construct<QtJniTypes::AdvertiseDataBuilder>();
// device name cannot be set but there is choice to show it or not
builder = builder.callMethod<QtJniTypes::AdvertiseDataBuilder>(
"setIncludeDeviceName", !data.localName().isEmpty());
builder = builder.callMethod<QtJniTypes::AdvertiseDataBuilder>(
"setIncludeTxPowerLevel", data.includePowerLevel());
const auto services = data.services();
for (const auto &service : services) {
builder = builder.callMethod<QtJniTypes::AdvertiseDataBuilder>("addServiceUuid",
javaParcelUuidfromQtUuid(service).object<QtJniTypes::ParcelUuid>());
}
if (!data.manufacturerData().isEmpty()) {
QJniEnvironment env;
const jsize nativeSize = q26::saturate_cast<jsize>(data.manufacturerData().size());
jbyteArray nativeData = env->NewByteArray(nativeSize);
env->SetByteArrayRegion(nativeData, 0, nativeSize,
reinterpret_cast<const jbyte*>(data.manufacturerData().constData()));
builder = builder.callMethod<QtJniTypes::AdvertiseDataBuilder>(
"addManufacturerData", jint(data.manufacturerId()), nativeData);
env->DeleteLocalRef(nativeData);
if (!builder.isValid()) {
qCWarning(QT_BT_ANDROID) << "Cannot set manufacturer id/data";
}
}
/*// TODO Qt vs Java API mismatch
-> Qt assumes rawData() is a global field
-> Android pairs rawData() per service uuid
if (!data.rawData().isEmpty()) {
QJniEnvironment env;
const jsize nativeSize = q26::saturate_cast<jsize>(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.checkAndClearExceptions()) {
qCWarning(QT_BT_ANDROID) << "Cannot set advertisement raw data";
}
}*/
QJniObject javaAdvertiseData = builder.callMethod<QtJniTypes::AdvertiseData>("build");
return javaAdvertiseData;
}
static QJniObject createJavaAdvertisingSetParameters(const QLowEnergyAdvertisingParameters &params,
const QJniObject &javaObj)
{
QJniObject builder = QJniObject::construct<QtJniTypes::AdvertisingSetParametersBuilder>();
// TODO: Extended advertising support (see QTBUG-144522).
// We need a separate setting in the public API for it, because
// old BTLE devices will not be able to understand it.
// For now, always disable it!
#if 0
const bool supportsExtendedAdvertising =
javaObj.callMethod<jboolean>("isLeExtendedAdvertisingSupported");
builder = builder.callMethod<QtJniTypes::AdvertisingSetParametersBuilder>(
"setLegacyMode", !supportsExtendedAdvertising);
#else
Q_UNUSED(javaObj);
builder = builder.callMethod<QtJniTypes::AdvertisingSetParametersBuilder>(
"setLegacyMode", true);
#endif
bool connectable = false;
bool scannable = false;
switch (params.mode())
{
case QLowEnergyAdvertisingParameters::AdvInd:
connectable = true;
scannable = true; // TODO: if extended advertising is enabled, it must be false!
break;
case QLowEnergyAdvertisingParameters::AdvScanInd:
connectable = false;
scannable = true;
break;
case QLowEnergyAdvertisingParameters::AdvNonConnInd:
connectable = false;
scannable = false;
break;
// intentionally no default case
}
builder = builder.callMethod<QtJniTypes::AdvertisingSetParametersBuilder>(
"setConnectable", connectable);
builder = builder.callMethod<QtJniTypes::AdvertisingSetParametersBuilder>(
"setScannable", scannable);
// The QLowEnergyAdvertisingParameters struct contains the min and max
// interval values in milliseconds. However, the Android docs state
// the following:
// Bluetooth LE Advertising interval, in 0.625ms unit.
// Valid range is from 160 (100ms) to 16777215 (10,485.759375 s).
auto advertisingSetParametersObj =
QJniEnvironment().findClass<QtJniTypes::AdvertisingSetParameters>();
static const int minAdvInterval =
QJniObject::getStaticField<jint>(advertisingSetParametersObj, "INTERVAL_MIN");
static const int maxAdvInterval =
QJniObject::getStaticField<jint>(advertisingSetParametersObj, "INTERVAL_MAX");
int desiredInterval = (params.minimumInterval() + params.maximumInterval()) / 2 / 0.625;
if (desiredInterval < minAdvInterval)
desiredInterval = minAdvInterval;
else if (desiredInterval > maxAdvInterval)
desiredInterval = maxAdvInterval;
builder = builder.callMethod<QtJniTypes::AdvertisingSetParametersBuilder>(
"setInterval", desiredInterval);
QJniObject javaAdvertisingSetParameters =
builder.callMethod<QtJniTypes::AdvertisingSetParameters>("build");
return javaAdvertisingSetParameters;
}
void QLowEnergyControllerPrivateAndroid::startAdvertising(const QLowEnergyAdvertisingParameters &params,
const QLowEnergyAdvertisingData &advertisingData,
const QLowEnergyAdvertisingData &scanResponseData)
{
setState(QLowEnergyController::AdvertisingState);
if (!ensureAndroidPermission(QBluetoothPermission::Access | QBluetoothPermission::Advertise)) {
qCWarning(QT_BT_ANDROID) << "startAdvertising() failed due to missing permissions";
setError(QLowEnergyController::MissingPermissionsError);
setState(QLowEnergyController::UnconnectedState);
return;
}
if (!hub || !hub->javaObject().isValid()) {
qCWarning(QT_BT_ANDROID) << "Cannot initiate QtBluetoothLEServer";
setError(QLowEnergyController::AdvertisingError);
setState(QLowEnergyController::UnconnectedState);
return;
}
// Pass on advertisingData, scanResponse & AdvertisingSetParameters
QJniObject jAdvertiseData = createJavaAdvertiseData(advertisingData);
QJniObject jScanResponse = createJavaAdvertiseData(scanResponseData);
QJniObject jAdvertisingSetParameters =
createJavaAdvertisingSetParameters(params, hub->javaObject());
const bool result = hub->javaObject().callMethod<jboolean>("startAdvertising",
jAdvertiseData.object<QtJniTypes::AdvertiseData>(),
jScanResponse.object<QtJniTypes::AdvertiseData>(),
jAdvertisingSetParameters.object<QtJniTypes::AdvertisingSetParameters>());
if (!result) {
setError(QLowEnergyController::AdvertisingError);
setState(QLowEnergyController::UnconnectedState);
}
}
void QLowEnergyControllerPrivateAndroid::stopAdvertising()
{
setState(QLowEnergyController::UnconnectedState);
hub->javaObject().callMethod<void>("stopAdvertising");
}
void QLowEnergyControllerPrivateAndroid::requestConnectionUpdate(const QLowEnergyConnectionParameters &params)
{
// Android does not permit specification of specific latency or min/max
// connection intervals (see BluetoothGatt.requestConnectionPriority()
// In fact, each device manufacturer is permitted to change those values via a config
// file too. Therefore we can only make an approximated guess (see implementation below)
// In addition there is no feedback signal (known bug) from the hardware layer as per v24.
// TODO recheck in later Android releases whether callback for
// BluetoothGatt.requestConnectionPriority() was added
if (role != QLowEnergyController::CentralRole) {
qCWarning(QT_BT_ANDROID) << "On Android, connection requests only work for central role";
return;
}
const bool result = hub->javaObject().callMethod<jboolean>("requestConnectionUpdatePriority",
params.minimumInterval());
if (!result)
qCWarning(QT_BT_ANDROID) << "Cannot set connection update priority";
}
/*
* Returns the Java char permissions based on the given characteristic data.
*/
static int setupCharPermissions(const QLowEnergyCharacteristicData &charData)
{
int permission = 0;
if (charData.properties() & QLowEnergyCharacteristic::Read) {
if (int(charData.readConstraints()) == 0 // nothing is equivalent to simple read
|| (charData.readConstraints()
& QBluetooth::AttAccessConstraint::AttAuthorizationRequired)) {
permission |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
"PERMISSION_READ");
}
if (charData.readConstraints()
& QBluetooth::AttAccessConstraint::AttAuthenticationRequired) {
permission |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
"PERMISSION_READ_ENCRYPTED");
}
if (charData.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
permission |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
"PERMISSION_READ_ENCRYPTED_MITM");
}
}
if (charData.properties() &
(QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse) ) {
if (int(charData.writeConstraints()) == 0 // no flag is equivalent ti simple write
|| (charData.writeConstraints()
& QBluetooth::AttAccessConstraint::AttAuthorizationRequired)) {
permission |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
"PERMISSION_WRITE");
}
if (charData.writeConstraints()
& QBluetooth::AttAccessConstraint::AttAuthenticationRequired) {
permission |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
"PERMISSION_WRITE_ENCRYPTED");
}
if (charData.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
permission |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
"PERMISSION_WRITE_ENCRYPTED_MITM");
}
}
if (charData.properties() & QLowEnergyCharacteristic::WriteSigned) {
if (charData.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
permission |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
"PERMISSION_WRITE_SIGNED_MITM");
} else {
permission |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
"PERMISSION_WRITE_SIGNED");
}
}
return permission;
}
/*
* Returns the Java desc permissions based on the given descriptor data.
*/
static int setupDescPermissions(const QLowEnergyDescriptorData &descData)
{
int permissions = 0;
if (descData.isReadable()) {
if (int(descData.readConstraints()) == 0 // empty is equivalent to simple read
|| (descData.readConstraints()
& QBluetooth::AttAccessConstraint::AttAuthorizationRequired)) {
permissions |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
"PERMISSION_READ");
}
if (descData.readConstraints()
& QBluetooth::AttAccessConstraint::AttAuthenticationRequired) {
permissions |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
"PERMISSION_READ_ENCRYPTED");
}
if (descData.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
permissions |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
"PERMISSION_READ_ENCRYPTED_MITM");
}
}
if (descData.isWritable()) {
if (int(descData.readConstraints()) == 0 // empty is equivalent to simple read
|| (descData.readConstraints()
& QBluetooth::AttAccessConstraint::AttAuthorizationRequired)) {
permissions |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
"PERMISSION_WRITE");
}
if (descData.readConstraints()
& QBluetooth::AttAccessConstraint::AttAuthenticationRequired) {
permissions |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
"PERMISSION_WRITE_ENCRYPTED");
}
if (descData.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
permissions |= QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
"PERMISSION_WRITE_ENCRYPTED_MITM");
}
}
return permissions;
}
void QLowEnergyControllerPrivateAndroid::addToGenericAttributeList(const QLowEnergyServiceData &serviceData,
QLowEnergyHandle startHandle)
{
QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(startHandle);
if (service.isNull())
return;
// create BluetoothGattService object
jint sType = QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattService>::className(),
"SERVICE_TYPE_PRIMARY");
if (serviceData.type() == QLowEnergyServiceData::ServiceTypeSecondary)
sType = QJniObject::getStaticField<jint>(
QtJniTypes::Traits<QtJniTypes::BluetoothGattService>::className(),
"SERVICE_TYPE_SECONDARY");
service->androidService = QJniObject::construct<QtJniTypes::BluetoothGattService>(
javaUuidfromQtUuid(service->uuid).object<QtJniTypes::UUID>(), 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",
includedServiceEntry->d_ptr->androidService.object<QtJniTypes::BluetoothGattService>());
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) {
// User may have set minimum and/or maximum characteristic value size. Enforce
// them here. If the user has not defined these limits, the default values 0..INT_MAX
// do not limit anything. If the sizes are violated here (ie. when adding the
// characteristics), regard it as a programming error.
if (charData.value().size() < charData.minimumValueLength() ||
charData.value().size() > charData.maximumValueLength()) {
qWarning() << "Warning: Ignoring characteristic" << charData.uuid()
<< "with invalid length:" << charData.value().size()
<< "(minimum:" << charData.minimumValueLength()
<< "maximum:" << charData.maximumValueLength() << ").";
continue;
}
// Issue a warning if the attribute length exceeds the bluetooth standard limit.
if (charData.value().size() > BTLE_MAX_ATTRIBUTE_VALUE_SIZE) {
qCWarning(QT_BT_ANDROID) << "Warning: characteristic" << charData.uuid() << "size"
<< "exceeds the standard: " << BTLE_MAX_ATTRIBUTE_VALUE_SIZE
<< ", value size:" << charData.value().size();
}
QJniObject javaChar = QJniObject::construct<QtJniTypes::QtBtGattCharacteristic>(
javaUuidfromQtUuid(charData.uuid()).object<QtJniTypes::UUID>(),
int(charData.properties()),
setupCharPermissions(charData),
charData.minimumValueLength(),
charData.maximumValueLength());
QJniEnvironment env;
const jsize nativeCharSize = q26::saturate_cast<jsize>(charData.value().size());
jbyteArray jb = env->NewByteArray(nativeCharSize);
env->SetByteArrayRegion(jb, 0, nativeCharSize, (jbyte*)charData.value().data());
jboolean success = javaChar.callMethod<jboolean>("setLocalValue", jb);
if (!success)
qCWarning(QT_BT_ANDROID) << "Cannot setup initial characteristic value for " << charData.uuid();
env->DeleteLocalRef(jb);
const QList<QLowEnergyDescriptorData> descriptorList = charData.descriptors();
for (const auto &descData: descriptorList) {
QJniObject javaDesc = QJniObject::construct<QtJniTypes::QtBtGattDescriptor>(
javaUuidfromQtUuid(descData.uuid()).object<QtJniTypes::UUID>(),
setupDescPermissions(descData));
const jsize nativeDescSize = q26::saturate_cast<jsize>(descData.value().size());
jb = env->NewByteArray(nativeDescSize);
env->SetByteArrayRegion(jb, 0, nativeDescSize, (jbyte*)descData.value().data());
success = javaDesc.callMethod<jboolean>("setLocalValue", jb);
if (!success) {
qCWarning(QT_BT_ANDROID) << "Cannot setup initial descriptor value for "
<< descData.uuid() << "(char" << charData.uuid()
<< "on service " << service->uuid << ")";
}
env->DeleteLocalRef(jb);
success = javaChar.callMethod<jboolean>("addDescriptor",
javaDesc.object<QtJniTypes::BluetoothGattDescriptor>());
if (!success) {
qCWarning(QT_BT_ANDROID) << "Cannot add descriptor" << descData.uuid()
<< "to service" << service->uuid << "(char:"
<< charData.uuid() << ")";
}
}
success = service->androidService.callMethod<jboolean>(
"addCharacteristic",
javaChar.object<QtJniTypes::BluetoothGattCharacteristic>());
if (!success) {
qCWarning(QT_BT_ANDROID) << "Cannot add characteristic" << charData.uuid()
<< "to service" << service->uuid;
}
}
hub->javaObject().callMethod<void>("addService",
service->androidService.object<QtJniTypes::BluetoothGattService>());
}
int QLowEnergyControllerPrivateAndroid::mtu() const
{
if (!hub) {
qCWarning(QT_BT_ANDROID) << "could not determine MTU, hub is does not exist";
return -1;
} else {
int result = hub->javaObject().callMethod<int>("mtu");
qCDebug(QT_BT_ANDROID) << "MTU found to be" << result;
return result;
}
}
void QLowEnergyControllerPrivateAndroid::readRssi()
{
if (!hub || !hub->javaObject().callMethod<jboolean>("readRemoteRssi")) {
qCWarning(QT_BT_ANDROID) << "request to read RSSI failed";
setError(QLowEnergyController::RssiReadError);
return;
}
}
QT_END_NAMESPACE