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