Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qlowenergycontroller_android.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "android/androidutils_p.h"
6#include "android/jni_android_p.h"
7#include <QCoreApplication>
8#include <QtCore/QLoggingCategory>
9#include <QtCore/QJniEnvironment>
10#include <QtCore/QJniObject>
11#include <QtCore/q26numeric.h>
12#include <QtBluetooth/QLowEnergyServiceData>
13#include <QtBluetooth/QLowEnergyCharacteristicData>
14#include <QtBluetooth/QLowEnergyDescriptorData>
15#include <QtBluetooth/QLowEnergyAdvertisingData>
16#include <QtBluetooth/QLowEnergyAdvertisingParameters>
17#include <QtBluetooth/QLowEnergyConnectionParameters>
18
20
21Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
22
23// BT Core v5.3, 3.2.9, Vol 3, Part F
24const int BTLE_MAX_ATTRIBUTE_VALUE_SIZE = 512;
25
26// Conversion: QBluetoothUuid -> java.util.UUID
27static QJniObject javaUuidfromQtUuid(const QBluetoothUuid& uuid)
28{
29 // cut off leading and trailing brackets
30 const QString output = uuid.toString(QUuid::WithoutBraces);
31
32 QJniObject javaString = QJniObject::fromString(output);
33 QJniObject javaUuid = QJniObject::callStaticMethod<QtJniTypes::UUID>(
34 QtJniTypes::Traits<QtJniTypes::UUID>::className(), "fromString",
35 javaString.object<jstring>());
36
37 return javaUuid;
38}
39
40QLowEnergyControllerPrivateAndroid::QLowEnergyControllerPrivateAndroid()
42 hub(0)
43{
45}
46
48{
49 if (role == QLowEnergyController::PeripheralRole) {
50 if (hub)
51 hub->javaObject().callMethod<void>("disconnectServer");
52 }
53}
54
56{
57 const bool isPeripheral = (role == QLowEnergyController::PeripheralRole);
58
59 if (isPeripheral) {
60 qRegisterMetaType<QJniObject>();
61 hub = new LowEnergyNotificationHub(remoteDevice, isPeripheral, this);
62 // we only connect to the peripheral role specific signals
63 // TODO add connections as they get added later on
64 connect(hub, &LowEnergyNotificationHub::connectionUpdated,
65 this, &QLowEnergyControllerPrivateAndroid::connectionUpdated);
67 this, &QLowEnergyControllerPrivateAndroid::mtuChanged);
69 this, &QLowEnergyControllerPrivateAndroid::advertisementError);
70 connect(hub, &LowEnergyNotificationHub::serverCharacteristicChanged,
71 this, &QLowEnergyControllerPrivateAndroid::serverCharacteristicChanged);
72 connect(hub, &LowEnergyNotificationHub::serverDescriptorWritten,
73 this, &QLowEnergyControllerPrivateAndroid::serverDescriptorWritten);
74 } else {
75 hub = new LowEnergyNotificationHub(remoteDevice, isPeripheral, this);
76 // we only connect to the central role specific signals
77 connect(hub, &LowEnergyNotificationHub::connectionUpdated,
78 this, &QLowEnergyControllerPrivateAndroid::connectionUpdated);
80 this, &QLowEnergyControllerPrivateAndroid::mtuChanged);
81 connect(hub, &LowEnergyNotificationHub::servicesDiscovered,
82 this, &QLowEnergyControllerPrivateAndroid::servicesDiscovered);
83 connect(hub, &LowEnergyNotificationHub::serviceDetailsDiscoveryFinished,
84 this, &QLowEnergyControllerPrivateAndroid::serviceDetailsDiscoveryFinished);
85 connect(hub, &LowEnergyNotificationHub::characteristicRead,
86 this, &QLowEnergyControllerPrivateAndroid::characteristicRead);
87 connect(hub, &LowEnergyNotificationHub::descriptorRead,
88 this, &QLowEnergyControllerPrivateAndroid::descriptorRead);
89 connect(hub, &LowEnergyNotificationHub::characteristicWritten,
90 this, &QLowEnergyControllerPrivateAndroid::characteristicWritten);
91 connect(hub, &LowEnergyNotificationHub::descriptorWritten,
92 this, &QLowEnergyControllerPrivateAndroid::descriptorWritten);
93 connect(hub, &LowEnergyNotificationHub::characteristicChanged,
94 this, &QLowEnergyControllerPrivateAndroid::characteristicChanged);
95 connect(hub, &LowEnergyNotificationHub::serviceError,
96 this, &QLowEnergyControllerPrivateAndroid::serviceError);
98 this, &QLowEnergyControllerPrivateAndroid::remoteRssiRead);
99 }
100}
101
103{
104 if (!hub) {
105 qCCritical(QT_BT_ANDROID) << "connectToDevice() LE controller has not been initialized";
106 return;
107 }
108
109 if (!ensureAndroidPermission(QBluetoothPermission::Access)) {
110 // This is unlikely to happen as a valid local adapter is a precondition
111 setError(QLowEnergyController::MissingPermissionsError);
112 qCWarning(QT_BT_ANDROID) << "connectToDevice() failed due to missing permissions";
113 return;
114 }
115
116 // required to pass unit test on default backend
117 if (remoteDevice.isNull()) {
118 qCWarning(QT_BT_ANDROID) << "Invalid/null remote device address";
119 setError(QLowEnergyController::UnknownRemoteDeviceError);
120 return;
121 }
122
123 setState(QLowEnergyController::ConnectingState);
124
125 if (!hub->javaObject().isValid()) {
126 qCWarning(QT_BT_ANDROID) << "Cannot initiate QtBluetoothLE";
127 setError(QLowEnergyController::ConnectionError);
128 setState(QLowEnergyController::UnconnectedState);
129 return;
130 }
131
132 bool result = hub->javaObject().callMethod<jboolean>("connect");
133 if (!result) {
134 setError(QLowEnergyController::ConnectionError);
135 setState(QLowEnergyController::UnconnectedState);
136 return;
137 }
138}
139
141{
142 /* Catch an Android timeout bug. If the device is connecting but cannot
143 * physically connect it seems to ignore the disconnect call below.
144 * At least BluetoothGattCallback.onConnectionStateChange never
145 * arrives. The next BluetoothGatt.connect() works just fine though.
146 * */
147
148 QLowEnergyController::ControllerState oldState = state;
149 setState(QLowEnergyController::ClosingState);
150
151 if (hub) {
152 if (role == QLowEnergyController::PeripheralRole)
153 hub->javaObject().callMethod<void>("disconnectServer");
154 else
155 hub->javaObject().callMethod<void>("disconnect");
156 }
157
158 if (oldState == QLowEnergyController::ConnectingState)
159 setState(QLowEnergyController::UnconnectedState);
160}
161
163{
164 // No need to check bluetooth permissions here as 'connected' is a precondition
165
166 if (hub && hub->javaObject().callMethod<jboolean>("discoverServices")) {
167 qCDebug(QT_BT_ANDROID) << "Service discovery initiated";
168 } else {
169 //revert to connected state
170 setError(QLowEnergyController::NetworkError);
171 setState(QLowEnergyController::ConnectedState);
172 }
173}
174
176 const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode)
177{
178 Q_UNUSED(mode);
179 if (!serviceList.contains(service)) {
180 qCWarning(QT_BT_ANDROID) << "Discovery of unknown service" << service.toString()
181 << "not possible";
182 return;
183 }
184
185 if (!hub)
186 return;
187
188 QString tempUuid = service.toString(QUuid::WithoutBraces);
189
190 QJniEnvironment env;
191 QJniObject uuid = QJniObject::fromString(tempUuid);
192 bool readAllValues = mode == QLowEnergyService::FullDiscovery;
193 bool result = hub->javaObject().callMethod<jboolean>("discoverServiceDetails",
194 uuid.object<jstring>(),
195 readAllValues);
196 if (!result) {
197 QSharedPointer<QLowEnergyServicePrivate> servicePrivate =
198 serviceList.value(service);
199 if (!servicePrivate.isNull()) {
200 servicePrivate->setError(QLowEnergyService::UnknownError);
201 servicePrivate->setState(QLowEnergyService::RemoteService);
202 }
203 qCWarning(QT_BT_ANDROID) << "Cannot discover details for" << service.toString();
204 return;
205 }
206
207 qCDebug(QT_BT_ANDROID) << "Discovery of" << service << "started";
208}
209
211 const QSharedPointer<QLowEnergyServicePrivate> service,
212 const QLowEnergyHandle charHandle,
213 const QByteArray &newValue,
214 QLowEnergyService::WriteMode mode)
215{
216 //TODO don't ignore WriteWithResponse, right now we assume responses
217 Q_ASSERT(!service.isNull());
218
219 if (!service->characteristicList.contains(charHandle))
220 return;
221
222 QJniEnvironment env;
223 const jsize nativeSize = q26::saturate_cast<jsize>(newValue.size());
224 jbyteArray payload;
225 payload = env->NewByteArray(nativeSize);
226 env->SetByteArrayRegion(payload, 0, nativeSize,
227 (jbyte *)newValue.constData());
228
229 bool result = false;
230 if (hub) {
231 if (role == QLowEnergyController::CentralRole) {
232 qCDebug(QT_BT_ANDROID) << "Write characteristic with handle " << charHandle
233 << newValue.toHex() << "(service:" << service->uuid
234 << ", writeWithResponse:" << (mode == QLowEnergyService::WriteWithResponse)
235 << ", signed:" << (mode == QLowEnergyService::WriteSigned) << ")";
236 result = hub->javaObject().callMethod<jboolean>("writeCharacteristic",
237 charHandle, payload, jint(mode));
238 } else { // peripheral mode
239 qCDebug(QT_BT_ANDROID) << "Write server characteristic with handle " << charHandle
240 << newValue.toHex() << "(service:" << service->uuid;
241
242 const auto &characteristic = characteristicForHandle(charHandle);
243 if (characteristic.isValid()) {
244 const QJniObject charUuid = javaUuidfromQtUuid(characteristic.uuid());
245 result = hub->javaObject().callMethod<jboolean>(
246 "writeCharacteristic",
247 service->androidService.object<QtJniTypes::BluetoothGattService>(),
248 charUuid.object<QtJniTypes::UUID>(), payload);
249 if (result)
250 service->characteristicList[charHandle].value = newValue;
251 }
252 }
253 }
254
255 env->DeleteLocalRef(payload);
256
257 if (!result)
258 service->setError(QLowEnergyService::CharacteristicWriteError);
259}
260
262 const QSharedPointer<QLowEnergyServicePrivate> service,
263 const QLowEnergyHandle charHandle,
264 const QLowEnergyHandle descHandle,
265 const QByteArray &newValue)
266{
267 Q_ASSERT(!service.isNull());
268
269 QJniEnvironment env;
270 const jsize nativeSize = q26::saturate_cast<jsize>(newValue.size());
271 jbyteArray payload;
272 payload = env->NewByteArray(nativeSize);
273 env->SetByteArrayRegion(payload, 0, nativeSize,
274 (jbyte *)newValue.constData());
275
276 bool result = false;
277 if (hub) {
278 if (role == QLowEnergyController::CentralRole) {
279 qCDebug(QT_BT_ANDROID) << "Write descriptor with handle " << descHandle
280 << newValue.toHex() << "(service:" << service->uuid << ")";
281 result = hub->javaObject().callMethod<jboolean>("writeDescriptor",
282 descHandle, payload);
283 } else {
284 const auto &characteristic = characteristicForHandle(charHandle);
285 const auto &descriptor = descriptorForHandle(descHandle);
286 if (characteristic.isValid() && descriptor.isValid()) {
287 qCDebug(QT_BT_ANDROID) << "Write descriptor" << descriptor.uuid()
288 << "(service:" << service->uuid
289 << "char: " << characteristic.uuid() << ")";
290 const QJniObject charUuid = javaUuidfromQtUuid(characteristic.uuid());
291 const QJniObject descUuid = javaUuidfromQtUuid(descriptor.uuid());
292 result = hub->javaObject().callMethod<jboolean>(
293 "writeDescriptor",
294 service->androidService.object<QtJniTypes::BluetoothGattService>(),
295 charUuid.object<QtJniTypes::UUID>(), descUuid.object<QtJniTypes::UUID>(),
296 payload);
297 if (result)
298 service->characteristicList[charHandle].descriptorList[descHandle].value = newValue;
299 }
300 }
301 }
302
303 env->DeleteLocalRef(payload);
304
305 if (!result)
306 service->setError(QLowEnergyService::DescriptorWriteError);
307}
308
310 const QSharedPointer<QLowEnergyServicePrivate> service,
311 const QLowEnergyHandle charHandle)
312{
313 Q_ASSERT(!service.isNull());
314
315 if (!service->characteristicList.contains(charHandle))
316 return;
317
318 QJniEnvironment env;
319 bool result = false;
320 if (hub) {
321 qCDebug(QT_BT_ANDROID) << "Read characteristic with handle"
322 << charHandle << service->uuid;
323 result = hub->javaObject().callMethod<jboolean>("readCharacteristic", charHandle);
324 }
325
326 if (!result)
327 service->setError(QLowEnergyService::CharacteristicReadError);
328}
329
331 const QSharedPointer<QLowEnergyServicePrivate> service,
332 const QLowEnergyHandle /*charHandle*/,
333 const QLowEnergyHandle descriptorHandle)
334{
335 Q_ASSERT(!service.isNull());
336
337 QJniEnvironment env;
338 bool result = false;
339 if (hub) {
340 qCDebug(QT_BT_ANDROID) << "Read descriptor with handle"
341 << descriptorHandle << service->uuid;
342 result = hub->javaObject().callMethod<jboolean>("readDescriptor", descriptorHandle);
343 }
344
345 if (!result)
346 service->setError(QLowEnergyService::DescriptorReadError);
347}
348
349void QLowEnergyControllerPrivateAndroid::connectionUpdated(
350 QLowEnergyController::ControllerState newState,
351 QLowEnergyController::Error errorCode)
352{
353 qCDebug(QT_BT_ANDROID) << "Connection updated:"
354 << "error:" << errorCode
355 << "oldState:" << state
356 << "newState:" << newState;
357
358 if (role == QLowEnergyController::PeripheralRole)
359 peripheralConnectionUpdated(newState, errorCode);
360 else
361 centralConnectionUpdated(newState, errorCode);
362}
363
364void QLowEnergyControllerPrivateAndroid::mtuChanged(int mtu)
365{
366 Q_Q(QLowEnergyController);
367 qCDebug(QT_BT_ANDROID) << "MTU updated:"
368 << "mtu:" << mtu;
369 emit q->mtuChanged(mtu);
370}
371
372void QLowEnergyControllerPrivateAndroid::remoteRssiRead(int rssi, bool success)
373{
374 Q_Q(QLowEnergyController);
375 if (success) {
376 // BT Core v5.3, 7.5.4, Vol 4, Part E
377 // The LE RSSI can take values -127..127 => narrowing to qint16 is safe
378 emit q->rssiRead(rssi);
379 } else {
380 qCDebug(QT_BT_ANDROID) << "Reading remote RSSI failed";
381 setError(QLowEnergyController::RssiReadError);
382 }
383}
384
385// called if server/peripheral
386void QLowEnergyControllerPrivateAndroid::peripheralConnectionUpdated(
387 QLowEnergyController::ControllerState newState,
388 QLowEnergyController::Error errorCode)
389{
390 // Java errorCode can be larger than max QLowEnergyController::Error
391 if (errorCode > QLowEnergyController::AdvertisingError)
392 errorCode = QLowEnergyController::UnknownError;
393
394 if (errorCode != QLowEnergyController::NoError)
395 setError(errorCode);
396
397 const QLowEnergyController::ControllerState oldState = state;
398 setState(newState);
399
400 // disconnect implies stop of advertisement
401 if (newState == QLowEnergyController::UnconnectedState)
403
404 // The remote name and address may have changed
405 if (hub) {
406 remoteDevice = QBluetoothAddress(
407 hub->javaObject().callMethod<jstring>("remoteAddress").toString());
408 remoteName = hub->javaObject().callMethod<jstring>("remoteName").toString();
409 }
410
411 Q_Q(QLowEnergyController);
412 // Emit (dis)connected if the connection state changes
413 if (oldState == QLowEnergyController::ConnectedState
414 && newState != QLowEnergyController::ConnectedState) {
415 emit q->disconnected();
416 } else if (newState == QLowEnergyController::ConnectedState
417 && oldState != QLowEnergyController::ConnectedState) {
418 emit q->connected();
419 }
420}
421
422// called if client/central
423void QLowEnergyControllerPrivateAndroid::centralConnectionUpdated(
424 QLowEnergyController::ControllerState newState,
425 QLowEnergyController::Error errorCode)
426{
427 Q_Q(QLowEnergyController);
428
429 const QLowEnergyController::ControllerState oldState = state;
430
431 if (errorCode != QLowEnergyController::NoError) {
432 // ConnectionError if transition from Connecting to Connected
433 if (oldState == QLowEnergyController::ConnectingState) {
434 setError(QLowEnergyController::ConnectionError);
435 /* There is a bug in Android, when connecting to an unconnectable
436 * device. The connection times out and Android sends error code
437 * 133 (doesn't exist) and STATE_CONNECTED. A subsequent disconnect()
438 * call never sends a STATE_DISCONNECTED either.
439 * As workaround we will trigger disconnect when we encounter
440 * error during connect attempt. This leaves the controller
441 * in a cleaner state.
442 * */
443 newState = QLowEnergyController::UnconnectedState;
444 }
445 else
446 setError(errorCode);
447 }
448
449 setState(newState);
450 if (newState == QLowEnergyController::UnconnectedState
451 && !(oldState == QLowEnergyController::UnconnectedState
452 || oldState == QLowEnergyController::ConnectingState)) {
453
454 // Invalidate the services if the disconnect came from the remote end.
455 // Qtherwise we disconnected via QLowEnergyController::disconnectDevice() which
456 // triggered invalidation already
457 if (!serviceList.isEmpty()) {
458 Q_ASSERT(oldState != QLowEnergyController::ClosingState);
460 }
461 emit q->disconnected();
462 } else if (newState == QLowEnergyController::ConnectedState
463 && oldState != QLowEnergyController::ConnectedState ) {
464 emit q->connected();
465 }
466}
467
468void QLowEnergyControllerPrivateAndroid::servicesDiscovered(
469 QLowEnergyController::Error errorCode, const QString &foundServices)
470{
471 Q_Q(QLowEnergyController);
472
473 if (errorCode == QLowEnergyController::NoError) {
474 //Android delivers all services in one go
475 const QStringList list = foundServices.split(QChar::Space, Qt::SkipEmptyParts);
476 for (const QString &entry : list) {
477 const QBluetoothUuid service(entry);
478 if (service.isNull())
479 return;
480
481 QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate();
482 priv->uuid = service;
483 priv->setController(this);
484
485 QSharedPointer<QLowEnergyServicePrivate> pointer(priv);
486 serviceList.insert(service, pointer);
487
488 emit q->serviceDiscovered(QBluetoothUuid(entry));
489 }
490
491 setState(QLowEnergyController::DiscoveredState);
492 emit q->discoveryFinished();
493 } else {
494 setError(errorCode);
495 setState(QLowEnergyController::ConnectedState);
496 }
497}
498
499void QLowEnergyControllerPrivateAndroid::serviceDetailsDiscoveryFinished(
500 const QString &serviceUuid, int startHandle, int endHandle)
501{
502 const QBluetoothUuid service(serviceUuid);
503 if (!serviceList.contains(service)) {
504 qCWarning(QT_BT_ANDROID) << "Discovery done of unknown service:"
505 << service.toString();
506 return;
507 }
508
509 //update service data
510 QSharedPointer<QLowEnergyServicePrivate> pointer =
511 serviceList.value(service);
512 pointer->startHandle = startHandle;
513 pointer->endHandle = endHandle;
514
515 if (hub && hub->javaObject().isValid()) {
516 QJniObject uuid = QJniObject::fromString(serviceUuid);
517 QJniObject javaIncludes = hub->javaObject().callMethod<jstring>(
518 "includedServices", uuid.object<jstring>());
519 if (javaIncludes.isValid()) {
520 const QStringList list = javaIncludes.toString()
521 .split(QChar::Space, Qt::SkipEmptyParts);
522 for (const QString &entry : list) {
523 const QBluetoothUuid service(entry);
524 if (service.isNull())
525 return;
526
527 pointer->includedServices.append(service);
528
529 // update the type of the included service
530 QSharedPointer<QLowEnergyServicePrivate> otherService =
531 serviceList.value(service);
532 if (!otherService.isNull())
533 otherService->type |= QLowEnergyService::IncludedService;
534 }
535 }
536 }
537
538 qCDebug(QT_BT_ANDROID) << "Service" << serviceUuid << "discovered (start:"
539 << startHandle << "end:" << endHandle << ")" << pointer.data();
540
541 pointer->setState(QLowEnergyService::RemoteServiceDiscovered);
542}
543
544void QLowEnergyControllerPrivateAndroid::characteristicRead(
545 const QBluetoothUuid &serviceUuid, int handle,
546 const QBluetoothUuid &charUuid, int properties, const QByteArray &data)
547{
548 if (!serviceList.contains(serviceUuid))
549 return;
550
551 QSharedPointer<QLowEnergyServicePrivate> service =
552 serviceList.value(serviceUuid);
553 QLowEnergyHandle charHandle = handle;
554
555 QLowEnergyServicePrivate::CharData &charDetails =
556 service->characteristicList[charHandle];
557
558 //Android uses same property value as Qt which is the Bluetooth LE standard
559 charDetails.properties = QLowEnergyCharacteristic::PropertyType(properties);
560 charDetails.uuid = charUuid;
561 charDetails.value = data;
562 //value handle always one larger than characteristics value handle
563 charDetails.valueHandle = charHandle + 1;
564
565 if (service->state == QLowEnergyService::RemoteServiceDiscovered) {
566 QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
567 if (!characteristic.isValid()) {
568 qCWarning(QT_BT_ANDROID) << "characteristicRead: Cannot find characteristic";
569 return;
570 }
571 emit service->characteristicRead(characteristic, data);
572 }
573}
574
575void QLowEnergyControllerPrivateAndroid::descriptorRead(
576 const QBluetoothUuid &serviceUuid, const QBluetoothUuid &charUuid,
577 int descHandle, const QBluetoothUuid &descUuid, const QByteArray &data)
578{
579 if (!serviceList.contains(serviceUuid))
580 return;
581
582 QSharedPointer<QLowEnergyServicePrivate> service =
583 serviceList.value(serviceUuid);
584
585 bool entryUpdated = false;
586
587 CharacteristicDataMap::iterator charIt = service->characteristicList.begin();
588 for ( ; charIt != service->characteristicList.end(); ++charIt) {
589 QLowEnergyServicePrivate::CharData &charDetails = charIt.value();
590
591 if (charDetails.uuid != charUuid)
592 continue;
593
594 // new entry created if it doesn't exist
595 QLowEnergyServicePrivate::DescData &descDetails =
596 charDetails.descriptorList[descHandle];
597 descDetails.uuid = descUuid;
598 descDetails.value = data;
599 entryUpdated = true;
600 break;
601 }
602
603 if (!entryUpdated) {
604 qCWarning(QT_BT_ANDROID) << "Cannot find/update descriptor"
605 << descUuid << charUuid << serviceUuid;
606 } else if (service->state == QLowEnergyService::RemoteServiceDiscovered){
607 QLowEnergyDescriptor descriptor = descriptorForHandle(descHandle);
608 if (!descriptor.isValid()) {
609 qCWarning(QT_BT_ANDROID) << "descriptorRead: Cannot find descriptor";
610 return;
611 }
612 emit service->descriptorRead(descriptor, data);
613 }
614}
615
616void QLowEnergyControllerPrivateAndroid::characteristicWritten(
617 int charHandle, const QByteArray &data, QLowEnergyService::ServiceError errorCode)
618{
619 QSharedPointer<QLowEnergyServicePrivate> service =
620 serviceForHandle(charHandle);
621 if (service.isNull())
622 return;
623
624 qCDebug(QT_BT_ANDROID) << "Characteristic write confirmation" << service->uuid
625 << charHandle << data.toHex() << errorCode;
626
627 if (errorCode != QLowEnergyService::NoError) {
628 service->setError(errorCode);
629 return;
630 }
631
632 QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
633 if (!characteristic.isValid()) {
634 qCWarning(QT_BT_ANDROID) << "characteristicWritten: Cannot find characteristic";
635 return;
636 }
637
638 // only update cache when property is readable. Otherwise it remains
639 // empty.
640 if (characteristic.properties() & QLowEnergyCharacteristic::Read)
641 updateValueOfCharacteristic(charHandle, data, false);
642 emit service->characteristicWritten(characteristic, data);
643}
644
645void QLowEnergyControllerPrivateAndroid::descriptorWritten(
646 int descHandle, const QByteArray &data, QLowEnergyService::ServiceError errorCode)
647{
648 QSharedPointer<QLowEnergyServicePrivate> service =
649 serviceForHandle(descHandle);
650 if (service.isNull())
651 return;
652
653 qCDebug(QT_BT_ANDROID) << "Descriptor write confirmation" << service->uuid
654 << descHandle << data.toHex() << errorCode;
655
656 if (errorCode != QLowEnergyService::NoError) {
657 service->setError(errorCode);
658 return;
659 }
660
661 QLowEnergyDescriptor descriptor = descriptorForHandle(descHandle);
662 if (!descriptor.isValid()) {
663 qCWarning(QT_BT_ANDROID) << "descriptorWritten: Cannot find descriptor";
664 return;
665 }
666
667 updateValueOfDescriptor(descriptor.characteristicHandle(),
668 descHandle, data, false);
669 emit service->descriptorWritten(descriptor, data);
670}
671
672void QLowEnergyControllerPrivateAndroid::serverDescriptorWritten(
673 const QJniObject &jniDesc, const QByteArray &newValue)
674{
675 qCDebug(QT_BT_ANDROID) << "Server descriptor change notification" << newValue.toHex();
676
677 // retrieve service, char and desc uuids
678 const QJniObject jniChar = jniDesc.callMethod<QtJniTypes::BluetoothGattCharacteristic>(
679 "getCharacteristic");
680 if (!jniChar.isValid())
681 return;
682
683 const QJniObject jniService =
684 jniChar.callMethod<QtJniTypes::BluetoothGattService>("getService");
685 if (!jniService.isValid())
686 return;
687
688 QJniObject jniUuid = jniService.callMethod<QtJniTypes::UUID>("getUuid");
689 const QBluetoothUuid serviceUuid(jniUuid.toString());
690 if (serviceUuid.isNull())
691 return;
692
693 // TODO test if two service with same uuid exist
694 if (!localServices.contains(serviceUuid))
695 return;
696
697 jniUuid = jniChar.callMethod<QtJniTypes::UUID>("getUuid");
698 const QBluetoothUuid characteristicUuid(jniUuid.toString());
699 if (characteristicUuid.isNull())
700 return;
701
702 jniUuid = jniDesc.callMethod<QtJniTypes::UUID>("getUuid");
703 const QBluetoothUuid descriptorUuid(jniUuid.toString());
704 if (descriptorUuid.isNull())
705 return;
706
707 // find matching QLEDescriptor
708 auto servicePrivate = localServices.value(serviceUuid);
709 // TODO test if service contains two characteristics with same uuid
710 // or characteristic contains two descriptors with same uuid
711 const auto handleList = servicePrivate->characteristicList.keys();
712 for (const auto charHandle: handleList) {
713 const auto &charData = servicePrivate->characteristicList.value(charHandle);
714 if (charData.uuid != characteristicUuid)
715 continue;
716
717 const auto &descHandleList = charData.descriptorList.keys();
718 for (const auto descHandle: descHandleList) {
719 const auto &descData = charData.descriptorList.value(descHandle);
720 if (descData.uuid != descriptorUuid)
721 continue;
722
723 qCDebug(QT_BT_ANDROID) << "serverDescriptorChanged: Matching descriptor"
724 << descriptorUuid << "in char" << characteristicUuid
725 << "of service" << serviceUuid;
726
727 servicePrivate->characteristicList[charHandle].descriptorList[descHandle].value = newValue;
728
729 emit servicePrivate->descriptorWritten(
730 QLowEnergyDescriptor(servicePrivate, charHandle, descHandle),
731 newValue);
732 return;
733 }
734 }
735}
736
737void QLowEnergyControllerPrivateAndroid::characteristicChanged(
738 int charHandle, const QByteArray &data)
739{
740 QSharedPointer<QLowEnergyServicePrivate> service =
741 serviceForHandle(charHandle);
742 if (service.isNull())
743 return;
744
745 qCDebug(QT_BT_ANDROID) << "Characteristic change notification" << service->uuid
746 << charHandle << data.toHex() << "length:" << data.size();
747
748 QLowEnergyCharacteristic characteristic = characteristicForHandle(charHandle);
749 if (!characteristic.isValid()) {
750 qCWarning(QT_BT_ANDROID) << "characteristicChanged: Cannot find characteristic";
751 return;
752 }
753
754 // only update cache when property is readable. Otherwise it remains
755 // empty.
756 if (characteristic.properties() & QLowEnergyCharacteristic::Read)
757 updateValueOfCharacteristic(characteristic.attributeHandle(),
758 data, false);
759 emit service->characteristicChanged(characteristic, data);
760}
761
762void QLowEnergyControllerPrivateAndroid::serverCharacteristicChanged(
763 const QJniObject &characteristic, const QByteArray &newValue)
764{
765 qCDebug(QT_BT_ANDROID) << "Server characteristic change notification"
766 << newValue.toHex() << "length:" << newValue.size();
767
768 // match characteristic to servicePrivate
769 QJniObject service = characteristic.callMethod<QtJniTypes::BluetoothGattService>(
770 "getService");
771 if (!service.isValid())
772 return;
773
774 QJniObject jniUuid = service.callMethod<QtJniTypes::UUID>("getUuid");
775 QBluetoothUuid serviceUuid(jniUuid.toString());
776 if (serviceUuid.isNull())
777 return;
778
779 // TODO test if two service with same uuid exist
780 if (!localServices.contains(serviceUuid))
781 return;
782
783 auto servicePrivate = localServices.value(serviceUuid);
784
785 jniUuid = characteristic.callMethod<QtJniTypes::UUID>("getUuid");
786 QBluetoothUuid characteristicUuid(jniUuid.toString());
787 if (characteristicUuid.isNull())
788 return;
789
790 QLowEnergyHandle foundHandle = 0;
791 const auto handleList = servicePrivate->characteristicList.keys();
792 // TODO test if service contains two characteristics with same uuid
793 for (const auto handle: handleList) {
794 QLowEnergyServicePrivate::CharData &charData = servicePrivate->characteristicList[handle];
795 if (charData.uuid != characteristicUuid)
796 continue;
797
798 qCDebug(QT_BT_ANDROID) << "serverCharacteristicChanged: Matching characteristic"
799 << characteristicUuid << " on " << serviceUuid;
800 charData.value = newValue;
801 foundHandle = handle;
802 break;
803 }
804
805 if (!foundHandle)
806 return;
807
808 emit servicePrivate->characteristicChanged(
809 QLowEnergyCharacteristic(servicePrivate, foundHandle), newValue);
810}
811
812void QLowEnergyControllerPrivateAndroid::serviceError(
813 int attributeHandle, QLowEnergyService::ServiceError errorCode)
814{
815 // ignore call if it isn't really an error
816 if (errorCode == QLowEnergyService::NoError)
817 return;
818
819 QSharedPointer<QLowEnergyServicePrivate> service =
820 serviceForHandle(attributeHandle);
821 Q_ASSERT(!service.isNull());
822
823 // ATM we don't really use attributeHandle but later on we might
824 // want to associate the error code with a char or desc
825 service->setError(errorCode);
826}
827
828void QLowEnergyControllerPrivateAndroid::advertisementError(int errorCode)
829{
830 Q_Q(QLowEnergyController);
831
832 switch (errorCode)
833 {
834 case 1: // AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE
835 errorString = QLowEnergyController::tr("Advertisement data is larger than 31 bytes");
836 break;
837 case 2: // AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED
838 errorString = QLowEnergyController::tr("Advertisement feature not supported on the platform");
839 break;
840 case 3: // AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR
841 errorString = QLowEnergyController::tr("Error occurred trying to start advertising");
842 break;
843 case 4: // AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS
844 errorString = QLowEnergyController::tr("Failed due to too many advertisers");
845 break;
846 default:
847 errorString = QLowEnergyController::tr("Unknown advertisement error");
848 break;
849 }
850
851 error = QLowEnergyController::AdvertisingError;
852 emit q->errorOccurred(error);
853
854 // not relevant states in peripheral mode
855 Q_ASSERT(state != QLowEnergyController::DiscoveredState);
856 Q_ASSERT(state != QLowEnergyController::DiscoveringState);
857
858 switch (state)
859 {
860 case QLowEnergyController::UnconnectedState:
861 case QLowEnergyController::ConnectingState:
862 case QLowEnergyController::ConnectedState:
863 case QLowEnergyController::ClosingState:
864 // noop as remote is already connected or about to disconnect.
865 // when connection drops we reset to unconnected anyway
866 break;
867
868 case QLowEnergyController::AdvertisingState:
869 setState(QLowEnergyController::UnconnectedState);
870 break;
871 default:
872 break;
873 }
874}
875
876static QJniObject javaParcelUuidfromQtUuid(const QBluetoothUuid &uuid)
877{
878 QString output = uuid.toString();
879 // cut off leading and trailing brackets
880 output = output.mid(1, output.size()-2);
881
882 QJniObject javaString = QJniObject::fromString(output);
883 QJniObject parcelUuid = QJniObject::callStaticMethod<QtJniTypes::ParcelUuid>(
884 QtJniTypes::Traits<QtJniTypes::ParcelUuid>::className(), "fromString",
885 javaString.object<jstring>());
886
887 return parcelUuid;
888}
889
890static QJniObject createJavaAdvertiseData(const QLowEnergyAdvertisingData &data)
891{
892 QJniObject builder = QJniObject::construct<QtJniTypes::AdvertiseDataBuilder>();
893
894 // device name cannot be set but there is choice to show it or not
895 builder = builder.callMethod<QtJniTypes::AdvertiseDataBuilder>(
896 "setIncludeDeviceName", !data.localName().isEmpty());
897 builder = builder.callMethod<QtJniTypes::AdvertiseDataBuilder>(
898 "setIncludeTxPowerLevel", data.includePowerLevel());
899 const auto services = data.services();
900 for (const auto &service : services) {
901 builder = builder.callMethod<QtJniTypes::AdvertiseDataBuilder>("addServiceUuid",
902 javaParcelUuidfromQtUuid(service).object<QtJniTypes::ParcelUuid>());
903 }
904
905 if (!data.manufacturerData().isEmpty()) {
906 QJniEnvironment env;
907 const jsize nativeSize = q26::saturate_cast<jsize>(data.manufacturerData().size());
908 jbyteArray nativeData = env->NewByteArray(nativeSize);
909 env->SetByteArrayRegion(nativeData, 0, nativeSize,
910 reinterpret_cast<const jbyte*>(data.manufacturerData().constData()));
911 builder = builder.callMethod<QtJniTypes::AdvertiseDataBuilder>(
912 "addManufacturerData", jint(data.manufacturerId()), nativeData);
913 env->DeleteLocalRef(nativeData);
914
915 if (!builder.isValid()) {
916 qCWarning(QT_BT_ANDROID) << "Cannot set manufacturer id/data";
917 }
918 }
919
920 /*// TODO Qt vs Java API mismatch
921 -> Qt assumes rawData() is a global field
922 -> Android pairs rawData() per service uuid
923 if (!data.rawData().isEmpty()) {
924 QJniEnvironment env;
925 const jsize nativeSize = q26::saturate_cast<jsize>(data.rawData().size());
926 jbyteArray nativeData = env->NewByteArray(nativeSize);
927 env->SetByteArrayRegion(nativeData, 0, nativeSize,
928 reinterpret_cast<const jbyte*>(data.rawData().constData()));
929 builder = builder.callObjectMethod("addServiceData",
930 "(Landroid/os/ParcelUuid;[B])Landroid/bluetooth/le/AdvertiseData$Builder;",
931 data.rawData().object(), nativeData);
932 env->DeleteLocalRef(nativeData);
933
934 if (env.checkAndClearExceptions()) {
935 qCWarning(QT_BT_ANDROID) << "Cannot set advertisement raw data";
936 }
937 }*/
938
939 QJniObject javaAdvertiseData = builder.callMethod<QtJniTypes::AdvertiseData>("build");
940 return javaAdvertiseData;
941}
942
943static QJniObject createJavaAdvertiseSettings(const QLowEnergyAdvertisingParameters &params)
944{
945 QJniObject builder = QJniObject::construct<QtJniTypes::AdvertiseSettingsBuilder>();
946
947 bool connectable = false;
948 switch (params.mode())
949 {
950 case QLowEnergyAdvertisingParameters::AdvInd:
951 connectable = true;
952 break;
953 case QLowEnergyAdvertisingParameters::AdvScanInd:
954 case QLowEnergyAdvertisingParameters::AdvNonConnInd:
955 connectable = false;
956 break;
957 // intentionally no default case
958 }
959 builder = builder.callMethod<QtJniTypes::AdvertiseSettingsBuilder>(
960 "setConnectable", connectable);
961
962 /* TODO No Android API for further QLowEnergyAdvertisingParameters options
963 * Android TxPowerLevel, AdvertiseMode and Timeout not mappable to Qt
964 */
965
966 QJniObject javaAdvertiseSettings = builder.callMethod<QtJniTypes::AdvertiseSettings>("build");
967 return javaAdvertiseSettings;
968}
969
970
971void QLowEnergyControllerPrivateAndroid::startAdvertising(const QLowEnergyAdvertisingParameters &params,
972 const QLowEnergyAdvertisingData &advertisingData,
973 const QLowEnergyAdvertisingData &scanResponseData)
974{
975 setState(QLowEnergyController::AdvertisingState);
976
977 if (!ensureAndroidPermission(QBluetoothPermission::Access | QBluetoothPermission::Advertise)) {
978 qCWarning(QT_BT_ANDROID) << "startAdvertising() failed due to missing permissions";
979 setError(QLowEnergyController::MissingPermissionsError);
980 setState(QLowEnergyController::UnconnectedState);
981 return;
982 }
983
984 if (!hub || !hub->javaObject().isValid()) {
985 qCWarning(QT_BT_ANDROID) << "Cannot initiate QtBluetoothLEServer";
986 setError(QLowEnergyController::AdvertisingError);
987 setState(QLowEnergyController::UnconnectedState);
988 return;
989 }
990
991 // Pass on advertisingData, scanResponse & AdvertiseSettings
992 QJniObject jAdvertiseData = createJavaAdvertiseData(advertisingData);
993 QJniObject jScanResponse = createJavaAdvertiseData(scanResponseData);
994 QJniObject jAdvertiseSettings = createJavaAdvertiseSettings(params);
995
996 const bool result = hub->javaObject().callMethod<jboolean>("startAdvertising",
997 jAdvertiseData.object<QtJniTypes::AdvertiseData>(),
998 jScanResponse.object<QtJniTypes::AdvertiseData>(),
999 jAdvertiseSettings.object<QtJniTypes::AdvertiseSettings>());
1000 if (!result) {
1001 setError(QLowEnergyController::AdvertisingError);
1002 setState(QLowEnergyController::UnconnectedState);
1003 }
1004}
1005
1007{
1008 setState(QLowEnergyController::UnconnectedState);
1009 hub->javaObject().callMethod<void>("stopAdvertising");
1010}
1011
1012void QLowEnergyControllerPrivateAndroid::requestConnectionUpdate(const QLowEnergyConnectionParameters &params)
1013{
1014 // Android does not permit specification of specific latency or min/max
1015 // connection intervals (see BluetoothGatt.requestConnectionPriority()
1016 // In fact, each device manufacturer is permitted to change those values via a config
1017 // file too. Therefore we can only make an approximated guess (see implementation below)
1018 // In addition there is no feedback signal (known bug) from the hardware layer as per v24.
1019
1020 // TODO recheck in later Android releases whether callback for
1021 // BluetoothGatt.requestConnectionPriority() was added
1022
1023 if (role != QLowEnergyController::CentralRole) {
1024 qCWarning(QT_BT_ANDROID) << "On Android, connection requests only work for central role";
1025 return;
1026 }
1027
1028 const bool result = hub->javaObject().callMethod<jboolean>("requestConnectionUpdatePriority",
1029 params.minimumInterval());
1030 if (!result)
1031 qCWarning(QT_BT_ANDROID) << "Cannot set connection update priority";
1032}
1033
1034/*
1035 * Returns the Java char permissions based on the given characteristic data.
1036 */
1037static int setupCharPermissions(const QLowEnergyCharacteristicData &charData)
1038{
1039 int permission = 0;
1040 if (charData.properties() & QLowEnergyCharacteristic::Read) {
1041 if (int(charData.readConstraints()) == 0 // nothing is equivalent to simple read
1042 || (charData.readConstraints()
1043 & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)) {
1044 permission |= QJniObject::getStaticField<jint>(
1045 QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
1046 "PERMISSION_READ");
1047 }
1048
1049 if (charData.readConstraints()
1050 & QBluetooth::AttAccessConstraint::AttAuthenticationRequired) {
1051 permission |= QJniObject::getStaticField<jint>(
1052 QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
1053 "PERMISSION_READ_ENCRYPTED");
1054 }
1055
1056 if (charData.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
1057 permission |= QJniObject::getStaticField<jint>(
1058 QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
1059 "PERMISSION_READ_ENCRYPTED_MITM");
1060 }
1061 }
1062
1063 if (charData.properties() &
1064 (QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse) ) {
1065 if (int(charData.writeConstraints()) == 0 // no flag is equivalent ti simple write
1066 || (charData.writeConstraints()
1067 & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)) {
1068 permission |= QJniObject::getStaticField<jint>(
1069 QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
1070 "PERMISSION_WRITE");
1071 }
1072
1073 if (charData.writeConstraints()
1074 & QBluetooth::AttAccessConstraint::AttAuthenticationRequired) {
1075 permission |= QJniObject::getStaticField<jint>(
1076 QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
1077 "PERMISSION_WRITE_ENCRYPTED");
1078 }
1079
1080 if (charData.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
1081 permission |= QJniObject::getStaticField<jint>(
1082 QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
1083 "PERMISSION_WRITE_ENCRYPTED_MITM");
1084 }
1085 }
1086
1087 if (charData.properties() & QLowEnergyCharacteristic::WriteSigned) {
1088 if (charData.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
1089 permission |= QJniObject::getStaticField<jint>(
1090 QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
1091 "PERMISSION_WRITE_SIGNED_MITM");
1092 } else {
1093 permission |= QJniObject::getStaticField<jint>(
1094 QtJniTypes::Traits<QtJniTypes::BluetoothGattCharacteristic>::className(),
1095 "PERMISSION_WRITE_SIGNED");
1096 }
1097 }
1098
1099 return permission;
1100}
1101
1102/*
1103 * Returns the Java desc permissions based on the given descriptor data.
1104 */
1105static int setupDescPermissions(const QLowEnergyDescriptorData &descData)
1106{
1107 int permissions = 0;
1108
1109 if (descData.isReadable()) {
1110 if (int(descData.readConstraints()) == 0 // empty is equivalent to simple read
1111 || (descData.readConstraints()
1112 & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)) {
1113 permissions |= QJniObject::getStaticField<jint>(
1114 QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
1115 "PERMISSION_READ");
1116 }
1117
1118 if (descData.readConstraints()
1119 & QBluetooth::AttAccessConstraint::AttAuthenticationRequired) {
1120 permissions |= QJniObject::getStaticField<jint>(
1121 QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
1122 "PERMISSION_READ_ENCRYPTED");
1123 }
1124
1125 if (descData.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
1126 permissions |= QJniObject::getStaticField<jint>(
1127 QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
1128 "PERMISSION_READ_ENCRYPTED_MITM");
1129 }
1130 }
1131
1132 if (descData.isWritable()) {
1133 if (int(descData.readConstraints()) == 0 // empty is equivalent to simple read
1134 || (descData.readConstraints()
1135 & QBluetooth::AttAccessConstraint::AttAuthorizationRequired)) {
1136 permissions |= QJniObject::getStaticField<jint>(
1137 QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
1138 "PERMISSION_WRITE");
1139 }
1140
1141 if (descData.readConstraints()
1142 & QBluetooth::AttAccessConstraint::AttAuthenticationRequired) {
1143 permissions |= QJniObject::getStaticField<jint>(
1144 QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
1145 "PERMISSION_WRITE_ENCRYPTED");
1146 }
1147
1148 if (descData.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) {
1149 permissions |= QJniObject::getStaticField<jint>(
1150 QtJniTypes::Traits<QtJniTypes::BluetoothGattDescriptor>::className(),
1151 "PERMISSION_WRITE_ENCRYPTED_MITM");
1152 }
1153 }
1154
1155 return permissions;
1156}
1157
1158void QLowEnergyControllerPrivateAndroid::addToGenericAttributeList(const QLowEnergyServiceData &serviceData,
1159 QLowEnergyHandle startHandle)
1160{
1161 QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(startHandle);
1162 if (service.isNull())
1163 return;
1164
1165 // create BluetoothGattService object
1166 jint sType = QJniObject::getStaticField<jint>(
1167 QtJniTypes::Traits<QtJniTypes::BluetoothGattService>::className(),
1168 "SERVICE_TYPE_PRIMARY");
1169 if (serviceData.type() == QLowEnergyServiceData::ServiceTypeSecondary)
1170 sType = QJniObject::getStaticField<jint>(
1171 QtJniTypes::Traits<QtJniTypes::BluetoothGattService>::className(),
1172 "SERVICE_TYPE_SECONDARY");
1173
1174 service->androidService = QJniObject::construct<QtJniTypes::BluetoothGattService>(
1175 javaUuidfromQtUuid(service->uuid).object<QtJniTypes::UUID>(), sType);
1176
1177 // add included services, which must have been added earlier already
1178 const QList<QLowEnergyService*> includedServices = serviceData.includedServices();
1179 for (const auto includedServiceEntry: includedServices) {
1180 //TODO test this end-to-end
1181 const jboolean result = service->androidService.callMethod<jboolean>("addService",
1182 includedServiceEntry->d_ptr->androidService.object<QtJniTypes::BluetoothGattService>());
1183 if (!result)
1184 qWarning(QT_BT_ANDROID) << "Cannot add included service " << includedServiceEntry->serviceUuid()
1185 << "to current service" << service->uuid;
1186 }
1187
1188 // add characteristics
1189 const QList<QLowEnergyCharacteristicData> serviceCharsData = serviceData.characteristics();
1190 for (const auto &charData: serviceCharsData) {
1191
1192 // User may have set minimum and/or maximum characteristic value size. Enforce
1193 // them here. If the user has not defined these limits, the default values 0..INT_MAX
1194 // do not limit anything. If the sizes are violated here (ie. when adding the
1195 // characteristics), regard it as a programming error.
1196 if (charData.value().size() < charData.minimumValueLength() ||
1197 charData.value().size() > charData.maximumValueLength()) {
1198 qWarning() << "Warning: Ignoring characteristic" << charData.uuid()
1199 << "with invalid length:" << charData.value().size()
1200 << "(minimum:" << charData.minimumValueLength()
1201 << "maximum:" << charData.maximumValueLength() << ").";
1202 continue;
1203 }
1204
1205 // Issue a warning if the attribute length exceeds the bluetooth standard limit.
1206 if (charData.value().size() > BTLE_MAX_ATTRIBUTE_VALUE_SIZE) {
1207 qCWarning(QT_BT_ANDROID) << "Warning: characteristic" << charData.uuid() << "size"
1208 << "exceeds the standard: " << BTLE_MAX_ATTRIBUTE_VALUE_SIZE
1209 << ", value size:" << charData.value().size();
1210 }
1211
1212 QJniObject javaChar = QJniObject::construct<QtJniTypes::QtBtGattCharacteristic>(
1213 javaUuidfromQtUuid(charData.uuid()).object<QtJniTypes::UUID>(),
1214 int(charData.properties()),
1215 setupCharPermissions(charData),
1216 charData.minimumValueLength(),
1217 charData.maximumValueLength());
1218
1219 QJniEnvironment env;
1220 const jsize nativeCharSize = q26::saturate_cast<jsize>(charData.value().size());
1221 jbyteArray jb = env->NewByteArray(nativeCharSize);
1222 env->SetByteArrayRegion(jb, 0, nativeCharSize, (jbyte*)charData.value().data());
1223 jboolean success = javaChar.callMethod<jboolean>("setLocalValue", jb);
1224 if (!success)
1225 qCWarning(QT_BT_ANDROID) << "Cannot setup initial characteristic value for " << charData.uuid();
1226 env->DeleteLocalRef(jb);
1227
1228 const QList<QLowEnergyDescriptorData> descriptorList = charData.descriptors();
1229 for (const auto &descData: descriptorList) {
1230 QJniObject javaDesc = QJniObject::construct<QtJniTypes::QtBtGattDescriptor>(
1231 javaUuidfromQtUuid(descData.uuid()).object<QtJniTypes::UUID>(),
1232 setupDescPermissions(descData));
1233
1234 const jsize nativeDescSize = q26::saturate_cast<jsize>(descData.value().size());
1235 jb = env->NewByteArray(nativeDescSize);
1236 env->SetByteArrayRegion(jb, 0, nativeDescSize, (jbyte*)descData.value().data());
1237 success = javaDesc.callMethod<jboolean>("setLocalValue", jb);
1238 if (!success) {
1239 qCWarning(QT_BT_ANDROID) << "Cannot setup initial descriptor value for "
1240 << descData.uuid() << "(char" << charData.uuid()
1241 << "on service " << service->uuid << ")";
1242 }
1243
1244 env->DeleteLocalRef(jb);
1245
1246
1247 success = javaChar.callMethod<jboolean>("addDescriptor",
1248 javaDesc.object<QtJniTypes::BluetoothGattDescriptor>());
1249 if (!success) {
1250 qCWarning(QT_BT_ANDROID) << "Cannot add descriptor" << descData.uuid()
1251 << "to service" << service->uuid << "(char:"
1252 << charData.uuid() << ")";
1253 }
1254 }
1255
1256 success = service->androidService.callMethod<jboolean>(
1257 "addCharacteristic",
1258 javaChar.object<QtJniTypes::BluetoothGattCharacteristic>());
1259 if (!success) {
1260 qCWarning(QT_BT_ANDROID) << "Cannot add characteristic" << charData.uuid()
1261 << "to service" << service->uuid;
1262 }
1263 }
1264
1265 hub->javaObject().callMethod<void>("addService",
1266 service->androidService.object<QtJniTypes::BluetoothGattService>());
1267}
1268
1270{
1271 if (!hub) {
1272 qCWarning(QT_BT_ANDROID) << "could not determine MTU, hub is does not exist";
1273 return -1;
1274 } else {
1275 int result = hub->javaObject().callMethod<int>("mtu");
1276 qCDebug(QT_BT_ANDROID) << "MTU found to be" << result;
1277 return result;
1278 }
1279}
1280
1282{
1283 if (!hub || !hub->javaObject().callMethod<jboolean>("readRemoteRssi")) {
1284 qCWarning(QT_BT_ANDROID) << "request to read RSSI failed";
1285 setError(QLowEnergyController::RssiReadError);
1286 return;
1287 }
1288}
1289
1290QT_END_NAMESPACE
void mtuChanged(int mtu)
void remoteRssiRead(int rssi, bool success)
void advertisementError(int status)
void writeDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle, const QByteArray &newValue) override
void discoverServiceDetails(const QBluetoothUuid &service, QLowEnergyService::DiscoveryMode mode) override
void readDescriptor(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QLowEnergyHandle descriptorHandle) override
void startAdvertising(const QLowEnergyAdvertisingParameters &params, const QLowEnergyAdvertisingData &advertisingData, const QLowEnergyAdvertisingData &scanResponseData) override
void addToGenericAttributeList(const QLowEnergyServiceData &service, QLowEnergyHandle startHandle) override
void readCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle) override
void requestConnectionUpdate(const QLowEnergyConnectionParameters &params) override
void writeCharacteristic(const QSharedPointer< QLowEnergyServicePrivate > service, const QLowEnergyHandle charHandle, const QByteArray &newValue, QLowEnergyService::WriteMode mode) override
quint16 QLowEnergyHandle
Definition qbluetooth.h:42
static QJniObject javaParcelUuidfromQtUuid(const QBluetoothUuid &uuid)
static int setupCharPermissions(const QLowEnergyCharacteristicData &charData)
static QJniObject createJavaAdvertiseSettings(const QLowEnergyAdvertisingParameters &params)
static QJniObject createJavaAdvertiseData(const QLowEnergyAdvertisingData &data)
static QJniObject javaUuidfromQtUuid(const QBluetoothUuid &uuid)
static int setupDescPermissions(const QLowEnergyDescriptorData &descData)
void registerQLowEnergyControllerMetaType()