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