Warning: That file was not part of the compilation database. It may have many parsing errors.

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

Warning: That file was not part of the compilation database. It may have many parsing errors.