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
QtBluetoothLEServer.java
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
4package org.qtproject.qt.android.bluetooth;
5
6import android.bluetooth.BluetoothDevice;
7import android.bluetooth.BluetoothGattCharacteristic;
8import android.bluetooth.BluetoothGattDescriptor;
9import android.bluetooth.BluetoothGattService;
10import android.content.Context;
11import android.bluetooth.BluetoothAdapter;
12import android.bluetooth.BluetoothGatt;
13import android.bluetooth.BluetoothGattServer;
14import android.bluetooth.BluetoothGattServerCallback;
15import android.bluetooth.BluetoothManager;
16import android.bluetooth.BluetoothProfile;
17import android.bluetooth.le.AdvertiseCallback;
18import android.bluetooth.le.AdvertiseData;
19import android.bluetooth.le.AdvertiseSettings;
20import android.bluetooth.le.BluetoothLeAdvertiser;
21import android.os.Build;
22import android.util.Log;
23import android.util.Pair;
24
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.Iterator;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.ListIterator;
31import java.util.HashMap;
32import java.util.UUID;
33
34class QtBluetoothLEServer {
35 private static final String TAG = "QtBluetoothGattServer";
36
37 /* Pointer to the Qt object that "owns" the Java object */
38 @SuppressWarnings({"CanBeFinal", "WeakerAccess"})
39 long qtObject = 0;
40 @SuppressWarnings("WeakerAccess")
41
42 private Context qtContext = null;
43
44 // Bluetooth members
45 private BluetoothAdapter mBluetoothAdapter = null;
46 private BluetoothManager mBluetoothManager = null;
47 private BluetoothGattServer mGattServer = null;
48 private BluetoothLeAdvertiser mLeAdvertiser = null;
49
50 private ArrayList<BluetoothGattService> mPendingServiceAdditions =
51 new ArrayList<BluetoothGattService>();
52
53 private String mRemoteName = "";
54 // This function is called from Qt thread
55 synchronized String remoteName() {
56 return mRemoteName;
57 }
58
59 private String mRemoteAddress = "";
60 // This function is called from Qt thread
61 synchronized String remoteAddress() {
62 return mRemoteAddress;
63 }
64
65 // BT Core v5.3, 5.2.1, Vol 3, Part G
66 private static final int DEFAULT_LE_ATT_MTU = 23;
67 // Holds the currently supported/used MTU
68 private int mSupportedMtu = DEFAULT_LE_ATT_MTU;
69 // Implementation defined limit
70 private static final int MAX_PENDING_WRITE_COUNT = 1024;
71 // BT Core v5.3, 3.4.6.1, Vol 3, Part F
72 private static final int GATT_ERROR_PREPARE_QUEUE_FULL = 0x9;
73 // BT Core v5.3, 3.2.9, Vol 3, Part F
74 private static final int BTLE_MAX_ATTRIBUTE_VALUE_SIZE = 512;
75
76 // The class stores queued writes from the remote device. The writes are
77 // executed later when instructed to do so by onExecuteWrite() callback.
78 private class WriteEntry {
79 WriteEntry(BluetoothDevice remoteDevice, Object target) {
80 this.remoteDevice = remoteDevice;
81 this.target = target;
82 this.writes = new ArrayList<Pair<byte[], Integer>>();
83 }
84 // Returns true if this is a proper entry for given device + target
85 boolean match(BluetoothDevice device, Object target) {
86 return remoteDevice.equals(device) && target.equals(target);
87 }
88 final BluetoothDevice remoteDevice; // Device that issued the writes
89 final Object target; // Characteristic or Descriptor
90 final List<Pair<byte[], Integer>> writes; // Value, offset
91 }
92 private final List<WriteEntry> mPendingPreparedWrites = new ArrayList<>();
93
94 // Helper function to clear the pending writes of a remote device. If the provided device
95 // is null, all writes are cleared
96 private void clearPendingPreparedWrites(Object device) {
97 if (device == null)
98 mPendingPreparedWrites.clear();
99 ListIterator<WriteEntry> iterator = mPendingPreparedWrites.listIterator();
100 while (iterator.hasNext()) {
101 if (iterator.next().remoteDevice.equals(device))
102 iterator.remove();
103 }
104 }
105
106 // The function adds a 'prepared write' entry to target's queue. If the "target + device"
107 // didn't have a queue before (this being the first write), the queue is created.
108 // Targets must be either descriptors or characteristics.
109 private int addPendingPreparedWrite(BluetoothDevice device, Object target,
110 int offset, byte[] value) {
111 WriteEntry entry = null;
112 int currentWriteCount = 0;
113
114 // Try to find an existing matching entry. Also while looping, count
115 // the total number of writes so far in order to know if we exceed the
116 // write queue size we have set for ourselves
117 for (WriteEntry e : mPendingPreparedWrites) {
118 if (e.match(device, target))
119 entry = e;
120 currentWriteCount += e.writes.size();
121 }
122
123 // BT Core v5.3, 3.4.6.1, Vol 3, Part F
124 if (currentWriteCount > MAX_PENDING_WRITE_COUNT) {
125 Log.w(TAG, "Prepared write queue is full, returning an error.");
126 return GATT_ERROR_PREPARE_QUEUE_FULL;
127 }
128
129 // If no matching entry, create a new one. This means this is the first prepared
130 // write request to this "device + target" combination
131 if (entry == null)
132 mPendingPreparedWrites.add(entry = new WriteEntry(device, target));
133
134 // Append the newly received chunk of data along with its offset
135 entry.writes.add(new Pair<byte[], Integer>(value, offset));
136 return BluetoothGatt.GATT_SUCCESS;
137 }
138
139 /*
140 As per Bluetooth specification each connected device can have individual and persistent
141 Client characteristic configurations (see Bluetooth Spec 5.0 Vol 3 Part G 3.3.3.3)
142 This class manages the existing configurrations.
143 */
144 private class ClientCharacteristicManager {
145 private final HashMap<BluetoothGattCharacteristic, List<Entry>> notificationStore = new HashMap<BluetoothGattCharacteristic, List<Entry>>();
146
147 private class Entry {
148 BluetoothDevice device = null;
149 byte[] value = null;
150 boolean isConnected = false;
151 }
152
153 void insertOrUpdate(BluetoothGattCharacteristic characteristic,
154 BluetoothDevice device, byte[] newValue)
155 {
156 if (notificationStore.containsKey(characteristic)) {
157
158 List<Entry> entries = notificationStore.get(characteristic);
159 for (int i = 0; i < entries.size(); i++) {
160 if (entries.get(i).device.equals(device)) {
161 Entry e = entries.get(i);
162 e.value = newValue;
163 entries.set(i, e);
164 return;
165 }
166 }
167
168 // not match so far -> add device to list
169 Entry e = new Entry();
170 e.device = device;
171 e.value = newValue;
172 e.isConnected = true;
173 entries.add(e);
174 return;
175 }
176
177 // new characteristic
178 Entry e = new Entry();
179 e.device = device;
180 e.value = newValue;
181 e.isConnected = true;
182 List<Entry> list = new LinkedList<Entry>();
183 list.add(e);
184 notificationStore.put(characteristic, list);
185 }
186
187 /*
188 Marks client characteristic configuration entries as (in)active based the associated
189 devices general connectivity state.
190 This function avoids that existing configurations are not acted
191 upon when the associated device is not connected.
192 */
193 void markDeviceConnectivity(BluetoothDevice device, boolean isConnected)
194 {
195 final Iterator<BluetoothGattCharacteristic> keys = notificationStore.keySet().iterator();
196 while (keys.hasNext()) {
197 final BluetoothGattCharacteristic characteristic = keys.next();
198 final List<Entry> entries = notificationStore.get(characteristic);
199 if (entries == null)
200 continue;
201
202 ListIterator<Entry> charConfig = entries.listIterator();
203 while (charConfig.hasNext()) {
204 Entry e = charConfig.next();
205 if (e.device.equals(device))
206 e.isConnected = isConnected;
207 }
208 }
209 }
210
211 // Returns list of all BluetoothDevices which require notification or indication.
212 // No match returns an empty list.
213 List<BluetoothDevice> getToBeUpdatedDevices(BluetoothGattCharacteristic characteristic)
214 {
215 ArrayList<BluetoothDevice> result = new ArrayList<BluetoothDevice>();
216 if (!notificationStore.containsKey(characteristic))
217 return result;
218
219 final ListIterator<Entry> iter = notificationStore.get(characteristic).listIterator();
220 while (iter.hasNext())
221 result.add(iter.next().device);
222
223 return result;
224 }
225
226 // Returns null if no match; otherwise the configured actual client characteristic
227 // configuration value
228 byte[] valueFor(BluetoothGattCharacteristic characteristic, BluetoothDevice device)
229 {
230 if (!notificationStore.containsKey(characteristic))
231 return null;
232
233 List<Entry> entries = notificationStore.get(characteristic);
234 for (int i = 0; i < entries.size(); i++) {
235 final Entry entry = entries.get(i);
236 if (entry.device.equals(device) && entry.isConnected == true)
237 return entries.get(i).value;
238 }
239
240 return null;
241 }
242 }
243
244 private static final UUID CLIENT_CHARACTERISTIC_CONFIGURATION_UUID = UUID
245 .fromString("00002902-0000-1000-8000-00805f9b34fb");
246 ClientCharacteristicManager clientCharacteristicManager = new ClientCharacteristicManager();
247
248 QtBluetoothLEServer(Context context)
249 {
250 qtContext = context;
251 if (qtContext == null) {
252 Log.w(TAG, "Missing context object. Peripheral role disabled.");
253 return;
254 }
255
256 mBluetoothManager =
257 (BluetoothManager) qtContext.getSystemService(Context.BLUETOOTH_SERVICE);
258 if (mBluetoothManager == null) {
259 Log.w(TAG, "Bluetooth service not available. Peripheral role disabled.");
260 return;
261 }
262
263 mBluetoothAdapter = mBluetoothManager.getAdapter();
264 if (mBluetoothAdapter == null) {
265 Log.w(TAG, "Missing Bluetooth adapter. Peripheral role disabled.");
266 return;
267 }
268
269 Log.w(TAG, "Let's do BTLE Peripheral.");
270 }
271
272 // The following functions are synchronized callback handlers. The callbacks
273 // from Android are forwarded to these methods to synchronize member variable
274 // access with other threads (the Qt thread's JNI calls in particular).
275 //
276 // We use a single lock object (this server) for simplicity because:
277 // - Some variables may change and would thus not be suitable as locking objects but
278 // would require their own additional objects => overhead
279 // - Many accesses to shared variables are infrequent and the code paths are fast and
280 // deterministic meaning that long "wait times" on a lock should not happen
281 // - Typically several shared variables are accessed in a single code block.
282 // If each variable would be protected individually, the amount of (nested) locking
283 // would become quite unreasonable
284
285 synchronized void handleOnConnectionStateChange(BluetoothDevice device,
286 int status, int newState)
287 {
288 if (mGattServer == null) {
289 Log.w(TAG, "Ignoring connection state event, server is disconnected");
290 return;
291 }
292 // Multiple GATT devices may be connected. Check if we still have connected
293 // devices or not, and set the server state accordingly. Note: it seems we get
294 // notifications from all GATT clients, not just from the ones interested in
295 // the services provided by this BT LE Server. Furthermore the list of
296 // currently connected devices does not appear to be in any particular order.
297 List<BluetoothDevice> connectedDevices =
298 mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER);
299 Log.w(TAG, "Device " + device + " connection state: " + newState + ", status: "
300 + status + ", connected devices: " + connectedDevices);
301 // 0 == QLowEnergyController::UnconnectedState
302 // 2 == QLowEnergyController::ConnectedState
303 int qtControllerState = connectedDevices.size() > 0 ? 2 : 0;
304
305 switch (newState) {
306 case BluetoothProfile.STATE_CONNECTED:
307 clientCharacteristicManager.markDeviceConnectivity(device, true);
308 mRemoteName = device.getName();
309 mRemoteAddress = device.getAddress();
310 break;
311 case BluetoothProfile.STATE_DISCONNECTED:
312 clientCharacteristicManager.markDeviceConnectivity(device, false);
313 clearPendingPreparedWrites(device);
314 // Update the remoteAddress and remoteName if needed
315 if (device.getAddress().equals(mRemoteAddress)
316 && !connectedDevices.isEmpty()) {
317 mRemoteName = connectedDevices.get(0).getName();
318 mRemoteAddress = connectedDevices.get(0).getAddress();
319 }
320 break;
321 default:
322 // According to the API doc of this callback this should not happen
323 Log.w(TAG, "Unhandled connection state change: " + newState);
324 return;
325 }
326
327 // If last client disconnected, close down the server
328 if (qtControllerState == 0) { // QLowEnergyController::UnconnectedState
329 mPendingServiceAdditions.clear();
330 mGattServer.close();
331 mGattServer = null;
332 mRemoteName = "";
333 mRemoteAddress = "";
334 mSupportedMtu = DEFAULT_LE_ATT_MTU;
335 }
336
337 int qtErrorCode;
338 switch (status) {
339 case BluetoothGatt.GATT_SUCCESS:
340 qtErrorCode = 0;
341 break;
342 default:
343 Log.w(TAG, "Unhandled error code on peripheral connectionStateChanged: "
344 + status + " " + newState);
345 qtErrorCode = status;
346 break;
347 }
348
349 leConnectionStateChange(qtObject, qtErrorCode, qtControllerState);
350 }
351
352 synchronized void handleOnServiceAdded(int status, BluetoothGattService service)
353 {
354 if (mGattServer == null) {
355 Log.w(TAG, "Ignoring service addition event, server is disconnected");
356 return;
357 }
358
359 Log.d(TAG, "Service " + service.getUuid().toString() + " addition result: " + status);
360
361 // Remove the indicated service from the pending queue
362 ListIterator<BluetoothGattService> iterator = mPendingServiceAdditions.listIterator();
363 while (iterator.hasNext()) {
364 if (iterator.next().getUuid().equals(service.getUuid())) {
365 iterator.remove();
366 break;
367 }
368 }
369
370 // If there are more services in the queue, add the next whose add initiation succeeds
371 iterator = mPendingServiceAdditions.listIterator();
372 while (iterator.hasNext()) {
373 BluetoothGattService nextService = iterator.next();
374 if (mGattServer.addService(nextService)) {
375 break;
376 } else {
377 Log.w(TAG, "Adding service " + nextService.getUuid().toString() + " failed");
378 iterator.remove();
379 }
380 }
381 }
382
383 synchronized void handleOnCharacteristicReadRequest(BluetoothDevice device,
384 int requestId, int offset,
385 BluetoothGattCharacteristic characteristic)
386 {
387 if (mGattServer == null) {
388 Log.w(TAG, "Ignoring characteristic read, server is disconnected");
389 return;
390 }
391
392 byte[] characteristicData =
393 ((QtBluetoothGattCharacteristic)characteristic).getLocalValue();
394
395 try {
396 byte[] dataArray = Arrays.copyOfRange(characteristicData,
397 offset, characteristicData.length);
398 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
399 offset, dataArray);
400 } catch (Exception ex) {
401 Log.w(TAG, "onCharacteristicReadRequest: " + requestId + " "
402 + offset + " " + characteristicData.length);
403 ex.printStackTrace();
404 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE,
405 offset, null);
406 }
407 }
408
409 synchronized void handleOnCharacteristicWriteRequest(BluetoothDevice device,
410 int requestId,
411 BluetoothGattCharacteristic characteristic,
412 boolean preparedWrite, boolean responseNeeded,
413 int offset, byte[] value)
414 {
415 if (mGattServer == null) {
416 Log.w(TAG, "Ignoring characteristic write, server is disconnected");
417 return;
418 }
419 Log.w(TAG, "onCharacteristicWriteRequest " + preparedWrite + " " + offset + " "
420 + value.length);
421 final int minValueLen = ((QtBluetoothGattCharacteristic)characteristic).minValueLength;
422 final int maxValueLen = ((QtBluetoothGattCharacteristic)characteristic).maxValueLength;
423
424 int resultStatus = BluetoothGatt.GATT_SUCCESS;
425 boolean sendNotificationOrIndication = false;
426
427 if (!preparedWrite) { // regular write
428 // User may have defined minimum and maximum size for the value, which
429 // we enforce here. If the user has not defined these limits, the default
430 // values 0..INT_MAX do not limit anything.
431 if (value.length < minValueLen || value.length > maxValueLen) {
432 // BT Core v 5.3, 4.9.3, Vol 3, Part G
433 Log.w(TAG, "onCharacteristicWriteRequest invalid char value length: "
434 + value.length + ", min: " + minValueLen + ", max: " + maxValueLen);
435 resultStatus = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
436 } else if (offset == 0) {
437 ((QtBluetoothGattCharacteristic)characteristic).setLocalValue(value);
438 leServerCharacteristicChanged(qtObject, characteristic, value);
439 sendNotificationOrIndication = true;
440 } else {
441 // This should not really happen as per Bluetooth spec
442 Log.w(TAG, "onCharacteristicWriteRequest: !preparedWrite, offset "
443 + offset + ", Not supported");
444 resultStatus = BluetoothGatt.GATT_INVALID_OFFSET;
445 }
446 } else {
447 // BT Core v5.3, 3.4.6, Vol 3, Part F
448 // This is a prepared write which is used to write characteristics larger than
449 // MTU. We need to record all requests and execute them in one go once
450 // onExecuteWrite() is received. We use a queue to remember the pending
451 // requests.
452 resultStatus = addPendingPreparedWrite(device, characteristic, offset, value);
453 }
454
455 if (responseNeeded)
456 mGattServer.sendResponse(device, requestId, resultStatus, offset, value);
457 if (sendNotificationOrIndication)
458 sendNotificationsOrIndications(characteristic);
459 }
460
461 synchronized void handleOnDescriptorReadRequest(BluetoothDevice device, int requestId,
462 int offset, BluetoothGattDescriptor descriptor)
463 {
464 if (mGattServer == null) {
465 Log.w(TAG, "Ignoring descriptor read, server is disconnected");
466 return;
467 }
468
469 byte[] dataArray = ((QtBluetoothGattDescriptor)descriptor).getLocalValue();
470
471 try {
472 if (descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID)) {
473 dataArray = clientCharacteristicManager.valueFor(
474 descriptor.getCharacteristic(), device);
475 if (dataArray == null)
476 dataArray = ((QtBluetoothGattDescriptor)descriptor).getLocalValue();
477 }
478
479 dataArray = Arrays.copyOfRange(dataArray, offset, dataArray.length);
480 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
481 offset, dataArray);
482 } catch (Exception ex) {
483 Log.w(TAG, "onDescriptorReadRequest: " + requestId + " "
484 + offset + " " + dataArray.length);
485 ex.printStackTrace();
486 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE,
487 offset, null);
488 }
489 }
490
491 synchronized void handleOnDescriptorWriteRequest(BluetoothDevice device, int requestId,
492 BluetoothGattDescriptor descriptor, boolean preparedWrite,
493 boolean responseNeeded, int offset, byte[] value)
494 {
495 if (mGattServer == null) {
496 Log.w(TAG, "Ignoring descriptor write, server is disconnected");
497 return;
498 }
499
500 Log.w(TAG, "onDescriptorWriteRequest " + preparedWrite + " " + offset + " " + value.length);
501 int resultStatus = BluetoothGatt.GATT_SUCCESS;
502
503 if (!preparedWrite) { // regular write
504 if (offset == 0) {
505 if (descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIGURATION_UUID)) {
506 // If both IND and NTF are requested, resort to NTF only. BT
507 // specification does not prohibit nor mention using both, but it is
508 // unlikely what the client intended. Stack behaviours vary;
509 // Apple client-side stack does not allow this, while Bluez client-side
510 // stack erroneously sends this even if the developer only asked for
511 // the other. The 0x03 value is a bitwise combination of 0x01 and 0x02
512 // as per specification: BT Core v5.3, 3.3.3.3, Vol 3, Part G
513 if (value[0] == 0x03) {
514 Log.w(TAG, "Warning: In CCC of characteristic: "
515 + descriptor.getCharacteristic().getUuid()
516 + " enabling both NTF & IND requested, enabling NTF only.");
517 value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
518 }
519 clientCharacteristicManager.insertOrUpdate(
520 descriptor.getCharacteristic(),
521 device, value);
522 }
523 ((QtBluetoothGattDescriptor)descriptor).setLocalValue(value);
524 leServerDescriptorWritten(qtObject, descriptor, value);
525 } else {
526 // This should not really happen as per Bluetooth spec
527 Log.w(TAG, "onDescriptorWriteRequest: !preparedWrite, offset "
528 + offset + ", Not supported");
529 resultStatus = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED;
530 }
531 } else {
532 // BT Core v5.3, 3.4.6, Vol 3, Part F
533 // This is a prepared write which is used to write descriptors larger than MTU.
534 // We need to record all requests and execute them in one go once
535 // onExecuteWrite() is received. We use a queue to remember the pending
536 // requests.
537 resultStatus = addPendingPreparedWrite(device, descriptor, offset, value);
538 }
539
540 if (responseNeeded)
541 mGattServer.sendResponse(device, requestId, resultStatus, offset, value);
542 }
543
544 synchronized void handleOnExecuteWrite(BluetoothDevice device,
545 int requestId, boolean execute)
546 {
547 if (mGattServer == null) {
548 Log.w(TAG, "Ignoring execute write, server is disconnected");
549 return;
550 }
551
552 Log.w(TAG, "onExecuteWrite " + device + " " + requestId + " " + execute);
553
554 if (execute) {
555 // BT Core v5.3, 3.4.6.3, Vol 3, Part F
556 // Execute all pending prepared writes for the provided 'device'
557 for (WriteEntry entry : mPendingPreparedWrites) {
558 if (!entry.remoteDevice.equals(device))
559 continue;
560
561 byte[] newValue = null;
562 // The target can be a descriptor or a characteristic
563 byte[] currentValue = (entry.target instanceof BluetoothGattCharacteristic)
564 ? ((QtBluetoothGattCharacteristic)entry.target).getLocalValue()
565 : ((QtBluetoothGattDescriptor)entry.target).getLocalValue();
566
567 // Iterate writes and apply them to the currentValue in received order
568 for (Pair<byte[], Integer> write : entry.writes) {
569 // write.first is data, write.second.intValue() is offset. Check
570 // that the offset is not beyond the length of the current value
571 if (write.second.intValue() > currentValue.length) {
572 clearPendingPreparedWrites(device);
573 // BT Core v5.3, 3.4.6.3, Vol 3, Part F
574 mGattServer.sendResponse(device, requestId,
575 BluetoothGatt.GATT_INVALID_OFFSET,
576 0, null);
577 return;
578 }
579
580 // User may have defined value minimum and maximum sizes for
581 // characteristics, which we enforce here. If the user has not defined
582 // these limits, the default values 0..INT_MAX do not limit anything.
583 // The value size cannot decrease in prepared write (small write is a
584 // partial update) => no check for the minimum size limit here.
585 if (entry.target instanceof QtBluetoothGattCharacteristic &&
586 (write.second.intValue() + write.first.length >
587 ((QtBluetoothGattCharacteristic)entry.target).maxValueLength)) {
588 clearPendingPreparedWrites(device);
589 // BT Core v5.3, 3.4.6.3, Vol 3, Part F
590 mGattServer.sendResponse(device, requestId,
591 BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH,
592 0, null);
593 return;
594 }
595
596 // Determine the size of the new value as we may be extending the
597 // current value size
598 newValue = new byte[Math.max(write.second.intValue() +
599 write.first.length, currentValue.length)];
600 // Copy the current value to the newValue. We can't use the currentValue
601 // directly because the length of value might increase by this write
602 System.arraycopy(currentValue, 0, newValue, 0, currentValue.length);
603 // Apply this iteration's write to the newValue
604 System.arraycopy(write.first, 0, newValue, write.second.intValue(),
605 write.first.length);
606 // Update the currentValue as there may be more writes to apply
607 currentValue = newValue;
608 }
609
610 // Update value and inform the Qt/C++ side on the update
611 if (entry.target instanceof BluetoothGattCharacteristic) {
612 ((QtBluetoothGattCharacteristic)entry.target).setLocalValue(newValue);
613 leServerCharacteristicChanged(
614 qtObject, (BluetoothGattCharacteristic)entry.target, newValue);
615 } else {
616 ((QtBluetoothGattDescriptor)entry.target).setLocalValue(newValue);
617 leServerDescriptorWritten(
618 qtObject, (BluetoothGattDescriptor)entry.target, newValue);
619 }
620 }
621 }
622 // Either we executed all writes or were asked to cancel.
623 // In any case clear writes and respond.
624 clearPendingPreparedWrites(device);
625 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
626 }
627
628 synchronized void handleOnMtuChanged(BluetoothDevice device, int mtu)
629 {
630 if (mSupportedMtu == mtu)
631 return;
632 mSupportedMtu = mtu;
633 leMtuChanged(qtObject, mSupportedMtu);
634 }
635
636 /*
637 * Call back handler for the Gatt Server.
638 */
639 private BluetoothGattServerCallback mGattServerListener = new BluetoothGattServerCallback()
640 {
641 @Override
642 public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
643 super.onConnectionStateChange(device, status, newState);
644 handleOnConnectionStateChange(device, status, newState);
645 }
646
647 @Override
648 public void onServiceAdded(int status, BluetoothGattService service) {
649 super.onServiceAdded(status, service);
650 handleOnServiceAdded(status, service);
651 }
652
653 @Override
654 public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic)
655 {
656 super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
657 handleOnCharacteristicReadRequest(device, requestId, offset, characteristic);
658 }
659
660 @Override
661 public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic,
662 boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)
663 {
664 super.onCharacteristicWriteRequest(device, requestId, characteristic,
665 preparedWrite, responseNeeded, offset, value);
666 handleOnCharacteristicWriteRequest(device, requestId, characteristic,
667 preparedWrite, responseNeeded, offset, value);
668 }
669
670 @Override
671 public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor)
672 {
673 super.onDescriptorReadRequest(device, requestId, offset, descriptor);
674 handleOnDescriptorReadRequest(device, requestId, offset, descriptor);
675 }
676
677 @Override
678 public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor,
679 boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)
680 {
681 super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite,
682 responseNeeded, offset, value);
683 handleOnDescriptorWriteRequest(device, requestId, descriptor, preparedWrite,
684 responseNeeded, offset, value);
685 }
686
687 @Override
688 public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute)
689 {
690 super.onExecuteWrite(device, requestId, execute);
691 handleOnExecuteWrite(device, requestId, execute);
692 }
693
694 @Override
695 public void onNotificationSent(BluetoothDevice device, int status) {
696 super.onNotificationSent(device, status);
697 Log.w(TAG, "onNotificationSent" + device + " " + status);
698 }
699
700 @Override
701 public void onMtuChanged(BluetoothDevice device, int mtu) {
702 handleOnMtuChanged(device, mtu);
703 }
704 };
705
706 // This function is called from Qt thread
707 synchronized int mtu() {
708 return mSupportedMtu;
709 }
710
711 // This function is called from Qt thread
712 synchronized boolean connectServer()
713 {
714 if (mGattServer != null)
715 return true;
716
717 BluetoothManager manager = (BluetoothManager) qtContext.getSystemService(Context.BLUETOOTH_SERVICE);
718 if (manager == null) {
719 Log.w(TAG, "Bluetooth service not available.");
720 return false;
721 }
722
723 mGattServer = manager.openGattServer(qtContext, mGattServerListener);
724
725 return (mGattServer != null);
726 }
727
728 // This function is called from Qt thread
729 synchronized void disconnectServer()
730 {
731 if (mGattServer == null)
732 return;
733
734 clearPendingPreparedWrites(null);
735 mPendingServiceAdditions.clear();
736 mGattServer.close();
737 mGattServer = null;
738
739 mRemoteName = mRemoteAddress = "";
740 leConnectionStateChange(qtObject, 0 /*NoError*/,
741 0 /*QLowEnergyController::UnconnectedState*/);
742 }
743
744 // This function is called from Qt thread
745 boolean startAdvertising(AdvertiseData advertiseData,
746 AdvertiseData scanResponse,
747 AdvertiseSettings settings)
748 {
749 // Check that the bluetooth is on
750 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
751 Log.w(TAG, "StartAdvertising: Bluetooth not available or offline");
752 return false;
753 }
754
755 // According to Android doc this check should always precede the advertiser creation
756 if (mLeAdvertiser == null && mBluetoothAdapter.isMultipleAdvertisementSupported())
757 mLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
758
759 if (mLeAdvertiser == null) {
760 Log.w(TAG, "StartAdvertising: LE advertisement not supported");
761 return false;
762 }
763
764 if (!connectServer()) {
765 Log.w(TAG, "Server::startAdvertising: Cannot open GATT server");
766 return false;
767 }
768
769 Log.w(TAG, "Starting to advertise.");
770 mLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseListener);
771
772 return true;
773 }
774
775 // This function is called from Qt thread
776 void stopAdvertising()
777 {
778 if (mLeAdvertiser == null)
779 return;
780
781 mLeAdvertiser.stopAdvertising(mAdvertiseListener);
782 Log.w(TAG, "Advertisement stopped.");
783 }
784
785 // This function is called from Qt thread
786 synchronized void addService(BluetoothGattService service)
787 {
788 if (!connectServer()) {
789 Log.w(TAG, "Server::addService: Cannot open GATT server");
790 return;
791 }
792
793 // When we add a service, we must wait for onServiceAdded callback before adding the
794 // next one. If the pending service queue is empty it means that there are no ongoing
795 // service additions => add the service to the server. If there are services in the
796 // queue it means there is an initiated addition ongoing, and we only add to the queue.
797 if (mPendingServiceAdditions.isEmpty()) {
798 if (mGattServer.addService(service))
799 mPendingServiceAdditions.add(service);
800 else
801 Log.w(TAG, "Adding service " + service.getUuid().toString() + " failed.");
802 } else {
803 mPendingServiceAdditions.add(service);
804 }
805 }
806
807 // API-level < 33
808 @SuppressWarnings("deprecation")
809 private void notifyCharacteristicChange(BluetoothDevice device,
810 BluetoothGattCharacteristic characteristic,
811 boolean confirm)
812 {
813 mGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
814 }
815
816 /*
817 Check the client characteristics configuration for the given characteristic
818 and sends notifications or indications as per required.
819
820 This function is called from Qt and Java threads and calls must be protected
821 */
822 private void sendNotificationsOrIndications(BluetoothGattCharacteristic characteristic)
823 {
824 final ListIterator<BluetoothDevice> iter =
825 clientCharacteristicManager.getToBeUpdatedDevices(characteristic).listIterator();
826
827 // TODO This quick loop over multiple devices should be synced with onNotificationSent().
828 // The next notifyCharacteristicChanged() call must wait until onNotificationSent()
829 // was received. At this becomes an issue when the server accepts multiple remote
830 // devices at the same time.
831 while (iter.hasNext()) {
832 final BluetoothDevice device = iter.next();
833 final byte[] clientCharacteristicConfig =
834 clientCharacteristicManager.valueFor(characteristic, device);
835 if (clientCharacteristicConfig != null) {
836 if (Arrays.equals(clientCharacteristicConfig,
837 BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
838 if (Build.VERSION.SDK_INT >= 33) {
839 mGattServer.notifyCharacteristicChanged(device, characteristic, false,
840 ((QtBluetoothGattCharacteristic)characteristic).getLocalValue());
841 } else {
842 notifyCharacteristicChange(device, characteristic, false);
843 }
844 } else if (Arrays.equals(clientCharacteristicConfig,
845 BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)) {
846 if (Build.VERSION.SDK_INT >= 33) {
847 mGattServer.notifyCharacteristicChanged(device, characteristic, true,
848 ((QtBluetoothGattCharacteristic)characteristic).getLocalValue());
849 } else {
850 notifyCharacteristicChange(device, characteristic, true);
851 }
852 }
853 }
854 }
855 }
856
857 /*
858 Updates the local database value for the given characteristic with \a charUuid and
859 \a newValue. If notifications for this task are enabled an appropriate notification will
860 be send to the remote client.
861
862 This function is called from the Qt thread.
863 */
864 boolean writeCharacteristic(BluetoothGattService service, UUID charUuid, byte[] newValue)
865 {
866 BluetoothGattCharacteristic foundChar = null;
867 List<BluetoothGattCharacteristic> charList = service.getCharacteristics();
868 for (BluetoothGattCharacteristic iter: charList) {
869 if (iter.getUuid().equals(charUuid) && foundChar == null) {
870 foundChar = iter;
871 // don't break here since we want to check next condition below on next iteration
872 } else if (iter.getUuid().equals(charUuid)) {
873 Log.w(TAG, "Found second char with same UUID. Wrong char may have been selected.");
874 break;
875 }
876 }
877
878 if (foundChar == null) {
879 Log.w(TAG, "writeCharacteristic: update for unknown characteristic failed");
880 return false;
881 }
882
883 // User may have set minimum and/or maximum characteristic value size. Enforce
884 // them here. If the user has not defined these limits, the default values 0..INT_MAX
885 // do not limit anything.
886 final int minValueLength = ((QtBluetoothGattCharacteristic)foundChar).minValueLength;
887 final int maxValueLength = ((QtBluetoothGattCharacteristic)foundChar).maxValueLength;
888 if (newValue.length < minValueLength || newValue.length > maxValueLength) {
889 Log.w(TAG, "writeCharacteristic: invalid value length: "
890 + newValue.length + ", min: " + minValueLength + ", max: " + maxValueLength);
891 return false;
892 }
893
894 synchronized (this) // a value update might be in progress
895 {
896 ((QtBluetoothGattCharacteristic)foundChar).setLocalValue(newValue);
897 // Value is updated even if server is not connected, but notifying is not possible
898 if (mGattServer != null)
899 sendNotificationsOrIndications(foundChar);
900 }
901
902 return true;
903 }
904
905 /*
906 Updates the local database value for the given \a descUuid to \a newValue.
907
908 This function is called from the Qt thread.
909 */
910 boolean writeDescriptor(BluetoothGattService service, UUID charUuid, UUID descUuid,
911 byte[] newValue)
912 {
913 BluetoothGattDescriptor foundDesc = null;
914 BluetoothGattCharacteristic foundChar = null;
915 final List<BluetoothGattCharacteristic> charList = service.getCharacteristics();
916 for (BluetoothGattCharacteristic iter: charList) {
917 if (!iter.getUuid().equals(charUuid))
918 continue;
919
920 if (foundChar == null) {
921 foundChar = iter;
922 } else {
923 Log.w(TAG, "Found second char with same UUID. Wrong char may have been selected.");
924 break;
925 }
926 }
927
928 if (foundChar != null)
929 foundDesc = foundChar.getDescriptor(descUuid);
930
931 if (foundChar == null || foundDesc == null) {
932 Log.w(TAG, "writeDescriptor: update for unknown char or desc failed (" + foundChar + ")");
933 return false;
934 }
935
936 // we even write CLIENT_CHARACTERISTIC_CONFIGURATION_UUID this way as we choose
937 // to interpret the server's call as a change of the default value.
938 synchronized (this) // a value update might be in progress
939 {
940 ((QtBluetoothGattDescriptor)foundDesc).setLocalValue(newValue);
941 }
942
943 return true;
944 }
945
946 /*
947 * Call back handler for Advertisement requests.
948 */
949 private AdvertiseCallback mAdvertiseListener = new AdvertiseCallback()
950 {
951 @Override
952 public void onStartSuccess(AdvertiseSettings settingsInEffect) {
953 super.onStartSuccess(settingsInEffect);
954 }
955
956 @Override
957 public void onStartFailure(int errorCode) {
958 Log.e(TAG, "Advertising failure: " + errorCode);
959 super.onStartFailure(errorCode);
960
961 // changing errorCode here implies changes to errorCode handling on Qt side
962 int qtErrorCode = 0;
963 switch (errorCode) {
964 case AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED:
965 return; // ignore -> noop
966 case AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE:
967 Log.e(TAG, "Please reduce size of advertising data.");
968 qtErrorCode = 1;
969 break;
970 case AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED:
971 qtErrorCode = 2;
972 break;
973 default: // default maps to internal error
974 case AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR:
975 qtErrorCode = 3;
976 break;
977 case AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS:
978 qtErrorCode = 4;
979 break;
980 }
981
982 if (qtErrorCode > 0)
983 leServerAdvertisementError(qtObject, qtErrorCode);
984 }
985 };
986
987 native void leConnectionStateChange(long qtObject, int errorCode, int newState);
988 native void leMtuChanged(long qtObject, int mtu);
989 native void leServerAdvertisementError(long qtObject, int status);
990 native void leServerCharacteristicChanged(long qtObject,
991 BluetoothGattCharacteristic characteristic,
992 byte[] newValue);
993 native void leServerDescriptorWritten(long qtObject,
994 BluetoothGattDescriptor descriptor,
995 byte[] newValue);
996}
IOBluetoothDevice * device
constexpr qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:274
QPainter Context
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
static const QString context()
Definition java.cpp:398
@ BluetoothAdapter
@ BluetoothDevice
const int maxValueLength
Definition btutility.mm:32
typename C::iterator iterator
Q_CORE_EXPORT QtJniTypes::Service service()
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter * iter
EGLOutputLayerEXT EGLint EGLAttrib value
[3]
QNearFieldTarget::RequestId requestId
GLenum GLuint GLintptr offset
GLuint entry
GLuint64EXT * result
[6]
Object::Entry Entry
Definition main.cpp:243
EGLContext EGLenum target
QList< int > list
[14]
QSettings settings("MyCompany", "MyApp")
[11]
QNetworkAccessManager manager
[0]
socket write("GET / HTTP/1.0\r\n\r\n")