4package org.qtproject.qt.android.bluetooth;
6import android.bluetooth.BluetoothAdapter;
7import android.bluetooth.BluetoothDevice;
8import android.bluetooth.BluetoothGatt;
9import android.bluetooth.BluetoothGattCallback;
10import android.bluetooth.BluetoothGattCharacteristic;
11import android.bluetooth.BluetoothGattDescriptor;
12import android.bluetooth.BluetoothGattService;
13import android.bluetooth.BluetoothProfile;
14import android.bluetooth.BluetoothManager;
15import android.bluetooth.le.BluetoothLeScanner;
16import android.bluetooth.le.ScanCallback;
17import android.bluetooth.le.ScanFilter;
18import android.bluetooth.le.ScanResult;
19import android.bluetooth.le.ScanSettings;
20import android.bluetooth.BluetoothStatusCodes;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.os.Build;
26import android.os.Handler;
27import android.os.HandlerThread;
28import android.os.Looper;
29import android.util.Log;
30import java.lang.reflect.Constructor;
31import java.lang.reflect.Method;
32import java.util.concurrent.atomic.AtomicInteger;
34import java.util.ArrayList;
35import java.util.Hashtable;
36import java.util.LinkedList;
38import java.util.NoSuchElementException;
43 private static final String TAG =
"QtBluetoothGatt";
45 private boolean mLeScanRunning =
false;
47 private BluetoothGatt mBluetoothGatt =
null;
48 private HandlerThread mHandlerThread =
null;
49 private Handler mHandler =
null;
50 private Constructor<BluetoothGattCharacteristic> mCharacteristicConstructor =
null;
51 private String mRemoteGattAddress;
52 private final UUID clientCharacteristicUuid = UUID.fromString(
"00002902-0000-1000-8000-00805f9b34fb");
53 private final int MAX_MTU = 512;
54 private final int DEFAULT_MTU = 23;
55 private int mSupportedMtu = -1;
64 private int HANDLE_FOR_RESET = -1;
65 private int HANDLE_FOR_MTU_EXCHANGE = -2;
66 private int HANDLE_FOR_RSSI_READ = -3;
67 private AtomicInteger handleForTimeout =
new AtomicInteger(HANDLE_FOR_RESET);
69 private final int RUNNABLE_TIMEOUT = 3000;
70 private final Handler timeoutHandler =
new Handler(Looper.getMainLooper());
72 private BluetoothLeScanner mBluetoothLeScanner =
null;
74 private class TimeoutRunnable
implements Runnable {
75 TimeoutRunnable(
int handle) { pendingJobHandle =
handle; }
78 boolean timeoutStillValid = handleForTimeout.compareAndSet(pendingJobHandle, HANDLE_FOR_RESET);
79 if (timeoutStillValid) {
80 Log.w(
TAG,
"****** Timeout for request on handle " + (pendingJobHandle & 0xffff));
81 Log.w(
TAG,
"****** Looks like the peripheral does NOT act in " +
82 "accordance to Bluetooth 4.x spec.");
83 Log.w(
TAG,
"****** Please check server implementation. Continuing under " +
86 if (pendingJobHandle > HANDLE_FOR_RESET)
87 interruptCurrentIO(pendingJobHandle & 0xffff);
88 else if (pendingJobHandle < HANDLE_FOR_RESET)
89 interruptCurrentIO(pendingJobHandle);
94 private int pendingJobHandle = -1;
111 private synchronized void handleOnReceive(
Context context, Intent intent)
113 if (mBluetoothGatt ==
null)
117 if (
device ==
null || !
device.getAddress().equals(mBluetoothGatt.getDevice().getAddress()))
120 final int bondState = intent.getIntExtra(
BluetoothDevice.EXTRA_BOND_STATE, -1);
121 final int previousBondState =
125 if (pendingJob ==
null
130 timeoutHandler.removeCallbacksAndMessages(
null);
131 handleForTimeout.set(HANDLE_FOR_RESET);
134 if (pendingJob ==
null
139 readWriteQueue.addFirst(pendingJob);
151 device.getClass().getMethod(
"removeBond").invoke(
device);
152 }
catch (Exception ex) {
153 ex.printStackTrace();
158 @SuppressWarnings(
"deprecation")
161 if (Build.VERSION.SDK_INT >= 33)
167 private class BondStateBroadcastReceiver
extends BroadcastReceiver {
170 handleOnReceive(
context, intent);
173 private BroadcastReceiver bondStateBroadcastReceiver =
null;
176 @SuppressWarnings({
"CanBeFinal",
"WeakerAccess"})
178 @SuppressWarnings(
"WeakerAccess")
181 @SuppressWarnings(
"WeakerAccess")
186 (BluetoothManager)qtContext.getSystemService(
Context.BLUETOOTH_SERVICE);
190 mBluetoothAdapter =
manager.getAdapter();
191 if (mBluetoothAdapter ==
null)
194 mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
199 mRemoteGattAddress = remoteAddress;
209 boolean scanForLeDevice(
final boolean isEnabled) {
210 if (isEnabled == mLeScanRunning)
213 if (mBluetoothLeScanner ==
null) {
214 Log.w(TAG,
"Cannot start LE scan, no bluetooth scanner");
219 Log.d(TAG,
"Attempting to start BTLE scan");
220 ScanSettings.Builder settingsBuilder =
new ScanSettings.Builder();
221 settingsBuilder = settingsBuilder.setScanMode(ScanSettings.SCAN_MODE_BALANCED);
222 ScanSettings
settings = settingsBuilder.build();
226 mBluetoothLeScanner.startScan(filterList,
settings, leScanCallback);
227 mLeScanRunning =
true;
229 Log.d(TAG,
"Attempting to stop BTLE scan");
231 mBluetoothLeScanner.stopScan(leScanCallback);
232 }
catch (IllegalStateException isex) {
235 Log.d(TAG,
"Stopping LE scan not possible: " + isex.getMessage());
237 mLeScanRunning =
false;
240 return (mLeScanRunning == isEnabled);
243 private final ScanCallback leScanCallback =
new ScanCallback() {
245 public void onScanResult(
int callbackType, ScanResult
result) {
246 super.onScanResult(callbackType,
result);
247 leScanResult(qtObject,
result.getDevice(),
result.getRssi(),
result.getScanRecord().getBytes());
252 super.onBatchScanResults(
results);
254 leScanResult(qtObject,
result.getDevice(),
result.getRssi(),
result.getScanRecord().getBytes());
259 public void onScanFailed(
int errorCode) {
260 super.onScanFailed(errorCode);
261 Log.d(TAG,
"BTLE device scan failed with " + errorCode);
267 private synchronized void handleOnConnectionStateChange(BluetoothGatt gatt,
270 Log.d(TAG,
"Connection state changes to: " +
newState +
", status: " + status
271 +
", qtObject: " + (qtObject != 0));
275 int qLowEnergyController_State = 0;
278 case BluetoothProfile.STATE_DISCONNECTED:
279 if (bondStateBroadcastReceiver !=
null) {
280 qtContext.unregisterReceiver(bondStateBroadcastReceiver);
281 bondStateBroadcastReceiver =
null;
284 qLowEnergyController_State = 0;
289 if (mBluetoothGatt !=
null) {
290 mBluetoothGatt.close();
291 if (mHandler !=
null) {
292 mHandler.getLooper().quitSafely();
296 mBluetoothGatt =
null;
298 case BluetoothProfile.STATE_CONNECTED:
299 if (bondStateBroadcastReceiver ==
null) {
300 bondStateBroadcastReceiver =
new BondStateBroadcastReceiver();
301 qtContext.registerReceiver(bondStateBroadcastReceiver,
304 qLowEnergyController_State = 2;
310 case BluetoothGatt.GATT_SUCCESS:
313 case BluetoothGatt.GATT_FAILURE:
317 Log.w(TAG,
"Connection Error: Try to delay connect() call after previous activity");
323 Log.w(TAG,
"The remote host closed the connection");
331 Log.w(TAG,
"Unhandled error code on connectionStateChanged: "
336 leConnectionStateChange(qtObject, errorCode, qLowEnergyController_State);
339 private synchronized void handleOnServicesDiscovered(BluetoothGatt gatt,
int status) {
342 StringBuilder builder =
new StringBuilder();
344 case BluetoothGatt.GATT_SUCCESS:
347 for (BluetoothGattService service:
services) {
348 builder.append(
service.getUuid().toString()).append(
" ");
352 Log.w(TAG,
"Unhandled error code on onServicesDiscovered: " + status);
353 errorCode = status;
break;
355 leServicesDiscovered(qtObject, errorCode, builder.toString());
356 if (status == BluetoothGatt.GATT_SUCCESS)
357 scheduleMtuExchange();
360 private synchronized void handleOnCharacteristicRead(BluetoothGatt gatt,
361 BluetoothGattCharacteristic characteristic,
365 int foundHandle = handleForCharacteristic(characteristic);
366 if (foundHandle == -1 || foundHandle >= entries.size() ) {
367 Log.w(TAG,
"Cannot find characteristic read request for read notification - handle: " +
368 foundHandle +
" size: " + entries.size());
377 boolean requestTimedOut = !handleForTimeout.compareAndSet(
380 if (requestTimedOut) {
381 Log.w(TAG,
"Late char read reply after timeout was hit for handle " + foundHandle);
387 GattEntry
entry = entries.get(foundHandle);
388 final boolean isServiceDiscoveryRun = !
entry.valueKnown;
389 entry.valueKnown =
true;
391 if (status == BluetoothGatt.GATT_SUCCESS) {
394 leCharacteristicRead(qtObject,
395 characteristic.getService().getUuid().toString(),
396 foundHandle + 1, characteristic.getUuid().toString(),
397 characteristic.getProperties(),
value);
399 if (isServiceDiscoveryRun) {
400 Log.w(TAG,
"onCharacteristicRead during discovery error: " + status);
402 Log.d(TAG,
"Non-readable characteristic " + characteristic.getUuid() +
403 " for service " + characteristic.getService().getUuid());
404 leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(),
405 foundHandle + 1, characteristic.getUuid().toString(),
406 characteristic.getProperties(),
value);
409 final int characteristicReadError = 5;
410 leServiceError(qtObject, foundHandle + 1, characteristicReadError);
414 if (isServiceDiscoveryRun) {
417 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
418 if (serviceEntry.endHandle == foundHandle)
419 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
428 private synchronized void handleOnCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
429 android.bluetooth.BluetoothGattCharacteristic characteristic,
432 int handle = handleForCharacteristic(characteristic);
434 Log.w(TAG,
"onCharacteristicChanged: cannot find handle");
438 leCharacteristicChanged(qtObject,
handle+1,
value);
441 private synchronized void handleOnCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
442 android.bluetooth.BluetoothGattCharacteristic characteristic,
445 if (status != BluetoothGatt.GATT_SUCCESS)
446 Log.w(TAG,
"onCharacteristicWrite: error " + status);
448 int handle = handleForCharacteristic(characteristic);
450 Log.w(TAG,
"onCharacteristicWrite: cannot find handle");
454 boolean requestTimedOut = !handleForTimeout.compareAndSet(
457 if (requestTimedOut) {
458 Log.w(TAG,
"Late char write reply after timeout was hit for handle " +
handle);
467 case BluetoothGatt.GATT_SUCCESS:
476 value = pendingJob.newValue;
479 leCharacteristicWritten(qtObject,
handle+1,
value, errorCode);
483 private synchronized void handleOnDescriptorRead(android.bluetooth.BluetoothGatt gatt,
484 android.bluetooth.BluetoothGattDescriptor descriptor,
485 int status,
byte[] newValue)
487 int foundHandle = handleForDescriptor(descriptor);
488 if (foundHandle == -1 || foundHandle >= entries.size() ) {
489 Log.w(TAG,
"Cannot find descriptor read request for read notification - handle: " +
490 foundHandle +
" size: " + entries.size());
499 boolean requestTimedOut = !handleForTimeout.compareAndSet(
502 if (requestTimedOut) {
503 Log.w(TAG,
"Late descriptor read reply after timeout was hit for handle " +
510 GattEntry
entry = entries.get(foundHandle);
511 final boolean isServiceDiscoveryRun = !
entry.valueKnown;
512 entry.valueKnown =
true;
514 if (status == BluetoothGatt.GATT_SUCCESS) {
516 leDescriptorRead(qtObject,
517 descriptor.getCharacteristic().getService().getUuid().toString(),
518 descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
519 descriptor.getUuid().toString(), newValue);
521 if (isServiceDiscoveryRun) {
524 Log.w(TAG,
"onDescriptorRead during discovery error: " + status);
525 Log.d(TAG,
"Non-readable descriptor " + descriptor.getUuid() +
526 " for characteristic " + descriptor.getCharacteristic().getUuid() +
527 " for service " + descriptor.getCharacteristic().getService().getUuid());
528 leDescriptorRead(qtObject,
529 descriptor.getCharacteristic().getService().getUuid().toString(),
530 descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
531 descriptor.getUuid().toString(), newValue);
534 final int descriptorReadError = 6;
535 leServiceError(qtObject, foundHandle + 1, descriptorReadError);
540 if (isServiceDiscoveryRun) {
542 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
543 if (serviceEntry.endHandle == foundHandle) {
544 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
553 if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
557 if ((
value & 0x03) > 0) {
558 Log.d(TAG,
"Found descriptor with automatic notifications.");
559 mBluetoothGatt.setCharacteristicNotification(
560 descriptor.getCharacteristic(),
true);
571 private synchronized void handleOnDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
572 android.bluetooth.BluetoothGattDescriptor descriptor,
575 if (status != BluetoothGatt.GATT_SUCCESS)
576 Log.w(TAG,
"onDescriptorWrite: error " + status);
578 int handle = handleForDescriptor(descriptor);
580 boolean requestTimedOut = !handleForTimeout.compareAndSet(
583 if (requestTimedOut) {
584 Log.w(TAG,
"Late descriptor write reply after timeout was hit for handle " +
594 case BluetoothGatt.GATT_SUCCESS:
595 errorCode = 0;
break;
597 errorCode = 3;
break;
600 byte[]
value = pendingJob.newValue;
603 leDescriptorWritten(qtObject,
handle+1,
value, errorCode);
607 private synchronized void handleOnMtuChanged(android.bluetooth.BluetoothGatt gatt,
610 int previousMtu = mSupportedMtu;
611 if (status == BluetoothGatt.GATT_SUCCESS) {
612 Log.w(TAG,
"MTU changed to " + mtu);
615 Log.w(TAG,
"MTU change error " + status +
". New MTU " + mtu);
616 mSupportedMtu = DEFAULT_MTU;
618 if (previousMtu != mSupportedMtu)
619 leMtuChanged(qtObject, mSupportedMtu);
621 boolean requestTimedOut = !handleForTimeout.compareAndSet(
622 modifiedReadWriteHandle(HANDLE_FOR_MTU_EXCHANGE,
IoJobType.
Mtu), HANDLE_FOR_RESET);
623 if (requestTimedOut) {
624 Log.w(TAG,
"Late mtu reply after timeout was hit");
635 private synchronized void handleOnReadRemoteRssi(android.bluetooth.BluetoothGatt gatt,
636 int rssi,
int status)
638 Log.d(TAG,
"RSSI read callback, rssi: " +
rssi +
", status: " + status);
639 leRemoteRssiRead(qtObject,
rssi, status == BluetoothGatt.GATT_SUCCESS);
641 boolean requestTimedOut = !handleForTimeout.compareAndSet(
642 modifiedReadWriteHandle(HANDLE_FOR_RSSI_READ,
IoJobType.
Rssi), HANDLE_FOR_RESET);
643 if (requestTimedOut) {
644 Log.w(TAG,
"Late RSSI read reply after timeout was hit");
657 private final BluetoothGattCallback gattCallback =
new BluetoothGattCallback() {
659 public void onConnectionStateChange(BluetoothGatt gatt,
int status,
int newState) {
660 super.onConnectionStateChange(gatt, status,
newState);
661 handleOnConnectionStateChange(gatt, status,
newState);
665 public void onServicesDiscovered(BluetoothGatt gatt,
int status) {
666 super.onServicesDiscovered(gatt, status);
667 handleOnServicesDiscovered(gatt, status);
673 @SuppressWarnings(
"deprecation")
674 public
void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
675 android.bluetooth.BluetoothGattCharacteristic characteristic,
678 super.onCharacteristicRead(gatt, characteristic, status);
679 handleOnCharacteristicRead(gatt, characteristic, characteristic.getValue(), status);
684 public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
685 android.bluetooth.BluetoothGattCharacteristic characteristic,
691 handleOnCharacteristicRead(gatt, characteristic,
value, status);
695 public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
696 android.bluetooth.BluetoothGattCharacteristic characteristic,
699 super.onCharacteristicWrite(gatt, characteristic, status);
700 handleOnCharacteristicWrite(gatt, characteristic, status);
705 @SuppressWarnings(
"deprecation")
706 public
void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
707 android.bluetooth.BluetoothGattCharacteristic characteristic)
709 super.onCharacteristicChanged(gatt, characteristic);
710 handleOnCharacteristicChanged(gatt, characteristic, characteristic.getValue());
715 public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
716 android.bluetooth.BluetoothGattCharacteristic characteristic,
721 handleOnCharacteristicChanged(gatt, characteristic,
value);
726 @SuppressWarnings(
"deprecation")
727 public
void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
728 android.bluetooth.BluetoothGattDescriptor descriptor,
731 super.onDescriptorRead(gatt, descriptor, status);
732 handleOnDescriptorRead(gatt, descriptor, status, descriptor.getValue());
737 public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
738 android.bluetooth.BluetoothGattDescriptor descriptor,
744 handleOnDescriptorRead(gatt, descriptor, status,
value);
748 public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
749 android.bluetooth.BluetoothGattDescriptor descriptor,
752 super.onDescriptorWrite(gatt, descriptor, status);
753 handleOnDescriptorWrite(gatt, descriptor, status);
763 public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt,
int rssi,
int status)
765 super.onReadRemoteRssi(gatt,
rssi, status);
766 handleOnReadRemoteRssi(gatt,
rssi, status);
770 public void onMtuChanged(android.bluetooth.BluetoothGatt gatt,
int mtu,
int status)
772 super.onMtuChanged(gatt, mtu, status);
773 handleOnMtuChanged(gatt, mtu, status);
778 synchronized int mtu() {
779 if (mSupportedMtu == -1) {
782 return mSupportedMtu;
787 synchronized boolean readRemoteRssi() {
788 if (mBluetoothGatt ==
null)
794 ReadWriteJob newJob =
new ReadWriteJob();
798 if (!readWriteQueue.add(newJob)) {
799 Log.w(TAG,
"Cannot add remote RSSI read to queue" );
803 performNextIOThreaded();
808 synchronized boolean connect() {
811 if (mBluetoothAdapter ==
null) {
812 Log.w(TAG,
"Cannot connect, no bluetooth adapter");
817 mRemoteGattDevice = mBluetoothAdapter.getRemoteDevice(mRemoteGattAddress);
818 }
catch (IllegalArgumentException ex) {
819 Log.w(TAG,
"Remote address is not valid: " + mRemoteGattAddress);
826 if (Build.VERSION.SDK_INT >= 27) {
827 HandlerThread handlerThread =
new HandlerThread(
"QtBluetoothLEHandlerThread");
828 handlerThread.start();
829 mHandler =
new Handler(handlerThread.getLooper());
832 args[0] = android.content.Context.class;
833 args[1] =
boolean.class;
834 args[2] = android.bluetooth.BluetoothGattCallback.class;
837 args[5] = android.os.Handler.class;
840 Method connectMethod = mRemoteGattDevice.getClass().getDeclaredMethod(
"connectGatt",
args);
841 if (connectMethod !=
null) {
842 mBluetoothGatt = (BluetoothGatt) connectMethod.invoke(mRemoteGattDevice, qtContext,
false,
843 gattCallback, 2 , 1 , mHandler);
844 Log.w(TAG,
"Using Android v26 BluetoothDevice.connectGatt()");
846 }
catch (Exception ex) {
847 Log.w(TAG,
"connectGatt() v26 not available");
848 ex.printStackTrace();
851 if (mBluetoothGatt ==
null) {
852 mHandler.getLooper().quitSafely();
857 if (mBluetoothGatt ==
null) {
862 constr_args[0] = android.bluetooth.BluetoothGattService.class;
863 constr_args[1] = java.util.UUID.class;
864 constr_args[2] =
int.class;
865 constr_args[3] =
int.class;
866 constr_args[4] =
int.class;
867 mCharacteristicConstructor = BluetoothGattCharacteristic.class.getDeclaredConstructor(constr_args);
868 mCharacteristicConstructor.setAccessible(
true);
869 }
catch (NoSuchMethodException ex) {
870 Log.w(TAG,
"Unable get characteristic constructor. Buffer race condition are possible");
879 mRemoteGattDevice.connectGatt(qtContext,
false,
881 }
catch (IllegalArgumentException ex) {
882 Log.w(TAG,
"Gatt connection failed");
883 ex.printStackTrace();
886 return mBluetoothGatt !=
null;
890 synchronized void disconnect() {
891 if (mBluetoothGatt ==
null)
894 mBluetoothGatt.disconnect();
898 synchronized boolean discoverServices()
900 return mBluetoothGatt !=
null && mBluetoothGatt.discoverServices();
907 private class GattEntry
910 boolean valueKnown =
false;
911 BluetoothGattService service =
null;
912 BluetoothGattCharacteristic characteristic =
null;
913 BluetoothGattDescriptor descriptor =
null;
922 int associatedServiceHandle;
933 private class ReadWriteJob
937 int requestedWriteType;
942 private final Hashtable<UUID, List<Integer>> uuidToEntry =
new Hashtable<UUID, List<Integer>>(100);
944 private final ArrayList<GattEntry> entries =
new ArrayList<GattEntry>(100);
946 private final LinkedList<Integer> servicesToBeDiscovered =
new LinkedList<Integer>();
949 private final LinkedList<ReadWriteJob> readWriteQueue =
new LinkedList<ReadWriteJob>();
950 private ReadWriteJob pendingJob;
958 private int handleForCharacteristic(BluetoothGattCharacteristic characteristic)
960 if (characteristic ==
null)
963 List<Integer> handles = uuidToEntry.get(characteristic.getService().getUuid());
964 if (handles ==
null || handles.isEmpty())
968 int serviceHandle = handles.get(0);
972 for (
int i = serviceHandle+1; i < entries.size(); i++) {
973 entry = entries.get(i);
977 switch (
entry.type) {
979 case CharacteristicValue:
984 if (
entry.characteristic == characteristic)
989 }
catch (IndexOutOfBoundsException ex) { }
999 private int handleForDescriptor(BluetoothGattDescriptor descriptor)
1001 if (descriptor ==
null)
1004 List<Integer> handles = uuidToEntry.get(descriptor.getCharacteristic().getService().getUuid());
1005 if (handles ==
null || handles.isEmpty())
1009 int serviceHandle = handles.get(0);
1013 for (
int i = serviceHandle+1; i < entries.size(); i++) {
1014 entry = entries.get(i);
1018 switch (
entry.type) {
1019 case Characteristic:
1020 case CharacteristicValue:
1025 if (
entry.descriptor == descriptor)
1030 }
catch (IndexOutOfBoundsException ignored) { }
1035 private void populateHandles()
1040 GattEntry
entry =
null;
1042 for (BluetoothGattService service:
services) {
1043 GattEntry serviceEntry =
new GattEntry();
1045 serviceEntry.service =
service;
1046 entries.add(serviceEntry);
1049 int serviceHandle = entries.size() - 1;
1051 serviceEntry.associatedServiceHandle = serviceHandle;
1056 old =
new ArrayList<Integer>();
1057 old.add(entries.size()-1);
1058 uuidToEntry.put(
service.getUuid(), old);
1062 for (BluetoothGattCharacteristic characteristic: charList) {
1063 entry =
new GattEntry();
1065 entry.characteristic = characteristic;
1066 entry.associatedServiceHandle = serviceHandle;
1071 entry =
new GattEntry();
1073 entry.associatedServiceHandle = serviceHandle;
1074 entry.endHandle = entries.size();
1079 for (BluetoothGattDescriptor desc: descList) {
1080 entry =
new GattEntry();
1082 entry.descriptor =
desc;
1083 entry.associatedServiceHandle = serviceHandle;
1090 serviceEntry.endHandle = entries.size() - 1;
1093 entries.trimToSize();
1096 private void resetData()
1098 uuidToEntry.clear();
1100 servicesToBeDiscovered.clear();
1103 timeoutHandler.removeCallbacksAndMessages(
null);
1104 handleForTimeout.set(HANDLE_FOR_RESET);
1106 readWriteQueue.clear();
1111 synchronized boolean discoverServiceDetails(
String serviceUuid,
boolean fullDiscovery)
1113 Log.d(TAG,
"Discover service details for: " + serviceUuid +
", fullDiscovery: "
1114 + fullDiscovery +
", BluetoothGatt: " + (mBluetoothGatt !=
null));
1116 if (mBluetoothGatt ==
null)
1119 if (entries.isEmpty())
1125 UUID
service = UUID.fromString(serviceUuid);
1127 if (handles ==
null || handles.isEmpty()) {
1128 Log.w(TAG,
"Unknown service uuid for current device: " +
service.toString());
1133 serviceHandle = handles.get(0);
1134 entry = entries.get(serviceHandle);
1135 if (
entry ==
null) {
1136 Log.w(TAG,
"Service with UUID " +
service.toString() +
" not found");
1139 }
catch (IllegalArgumentException ex) {
1141 Log.w(TAG,
"Cannot parse given UUID");
1146 Log.w(TAG,
"Given UUID is not a service UUID: " + serviceUuid);
1151 if (
entry.valueKnown || servicesToBeDiscovered.contains(serviceHandle)) {
1152 Log.w(TAG,
"Service already known or to be discovered");
1156 servicesToBeDiscovered.add(serviceHandle);
1157 scheduleServiceDetailDiscovery(serviceHandle, fullDiscovery);
1158 performNextIOThreaded();
1159 }
catch (Exception ex) {
1160 ex.printStackTrace();
1171 synchronized String includedServices(
String serviceUuid)
1173 if (mBluetoothGatt ==
null)
1178 uuid = UUID.fromString(serviceUuid);
1179 }
catch (Exception ex) {
1180 ex.printStackTrace();
1185 BluetoothGattService
service = mBluetoothGatt.getService(uuid);
1186 if (service ==
null)
1190 if (includes.isEmpty())
1193 StringBuilder builder =
new StringBuilder();
1194 for (BluetoothGattService includedService: includes) {
1195 builder.append(includedService.getUuid().toString()).append(
" ");
1198 return builder.toString();
1201 private synchronized void finishCurrentServiceDiscovery(
int handleDiscoveredService)
1203 Log.w(TAG,
"Finished current discovery for service handle " + handleDiscoveredService);
1204 GattEntry discoveredService = entries.get(handleDiscoveredService);
1205 discoveredService.valueKnown =
true;
1207 servicesToBeDiscovered.removeFirst();
1208 }
catch (NoSuchElementException ex) {
1209 Log.w(TAG,
"Expected queued service but didn't find any");
1212 leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(),
1213 handleDiscoveredService + 1, discoveredService.endHandle + 1);
1218 private boolean executeMtuExchange()
1220 if (mBluetoothGatt.requestMtu(MAX_MTU)) {
1221 Log.w(TAG,
"MTU change initiated");
1224 Log.w(TAG,
"MTU change request failed");
1227 Log.w(TAG,
"Assuming default MTU value of 23 bytes");
1228 mSupportedMtu = DEFAULT_MTU;
1232 private boolean executeRemoteRssiRead()
1234 if (mBluetoothGatt.readRemoteRssi()) {
1235 Log.d(TAG,
"RSSI read initiated");
1238 Log.w(TAG,
"Initiating remote RSSI read failed");
1239 leRemoteRssiRead(qtObject, 0,
false);
1247 private void scheduleMtuExchange() {
1248 ReadWriteJob newJob =
new ReadWriteJob();
1250 newJob.entry =
null;
1252 readWriteQueue.add(newJob);
1264 private void scheduleServiceDetailDiscovery(
int serviceHandle,
boolean fullDiscovery)
1266 GattEntry serviceEntry = entries.get(serviceHandle);
1267 final int endHandle = serviceEntry.endHandle;
1269 if (serviceHandle == endHandle) {
1270 Log.w(TAG,
"scheduleServiceDetailDiscovery: service is empty; nothing to discover");
1271 finishCurrentServiceDiscovery(serviceHandle);
1276 for (
int i = serviceHandle + 1;
i <= endHandle;
i++) {
1277 GattEntry
entry = entries.get(i);
1281 Log.w(TAG,
"scheduleServiceDetailDiscovery: wrong endHandle");
1285 ReadWriteJob newJob =
new ReadWriteJob();
1286 newJob.entry =
entry;
1287 if (fullDiscovery) {
1293 final boolean result = readWriteQueue.add(newJob);
1295 Log.w(TAG,
"Cannot add service discovery job for " + serviceEntry.service.getUuid()
1296 +
" on item " +
entry.type);
1305 synchronized boolean writeCharacteristic(
int charHandle,
byte[] newValue,
1308 if (mBluetoothGatt ==
null)
1313 entry = entries.get(charHandle-1);
1314 }
catch (IndexOutOfBoundsException ex) {
1315 ex.printStackTrace();
1319 ReadWriteJob newJob =
new ReadWriteJob();
1320 newJob.newValue = newValue;
1321 newJob.entry =
entry;
1325 switch (writeMode) {
1327 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
1330 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_SIGNED;
1333 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
1338 result = readWriteQueue.add(newJob);
1341 Log.w(TAG,
"Cannot add characteristic write request for " + charHandle +
" to queue" );
1345 performNextIOThreaded();
1354 synchronized boolean writeDescriptor(
int descHandle,
byte[] newValue)
1356 if (mBluetoothGatt ==
null)
1361 entry = entries.get(descHandle-1);
1362 }
catch (IndexOutOfBoundsException ex) {
1363 ex.printStackTrace();
1367 ReadWriteJob newJob =
new ReadWriteJob();
1368 newJob.newValue = newValue;
1369 newJob.entry =
entry;
1370 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
1374 result = readWriteQueue.add(newJob);
1377 Log.w(TAG,
"Cannot add descriptor write request for " + descHandle +
" to queue" );
1381 performNextIOThreaded();
1390 synchronized boolean readCharacteristic(
int charHandle)
1392 if (mBluetoothGatt ==
null)
1397 entry = entries.get(charHandle-1);
1398 }
catch (IndexOutOfBoundsException ex) {
1399 ex.printStackTrace();
1403 ReadWriteJob newJob =
new ReadWriteJob();
1404 newJob.entry =
entry;
1408 result = readWriteQueue.add(newJob);
1411 Log.w(TAG,
"Cannot add characteristic read request for " + charHandle +
" to queue" );
1415 performNextIOThreaded();
1420 synchronized boolean readDescriptor(
int descHandle)
1422 if (mBluetoothGatt ==
null)
1427 entry = entries.get(descHandle-1);
1428 }
catch (IndexOutOfBoundsException ex) {
1429 ex.printStackTrace();
1433 ReadWriteJob newJob =
new ReadWriteJob();
1434 newJob.entry =
entry;
1438 result = readWriteQueue.add(newJob);
1441 Log.w(TAG,
"Cannot add descriptor read request for " + descHandle +
" to queue" );
1445 performNextIOThreaded();
1452 private synchronized void interruptCurrentIO(
int handle)
1457 performNextIOThreaded();
1459 if (
handle == HANDLE_FOR_MTU_EXCHANGE ||
handle == HANDLE_FOR_RSSI_READ)
1466 if (
entry.valueKnown)
1468 entry.valueKnown =
true;
1470 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
1471 if (serviceEntry !=
null && serviceEntry.endHandle ==
handle)
1472 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
1473 }
catch (IndexOutOfBoundsException outOfBounds) {
1474 Log.w(TAG,
"interruptCurrentIO(): Unknown gatt entry, index: "
1475 +
handle +
" size: " + entries.size());
1483 private void performNextIOThreaded()
1485 if (mHandler !=
null) {
1486 mHandler.post(
new Runnable() {
1502 private synchronized void performNextIO()
1504 Log.d(TAG,
"Perform next BTLE IO, job queue size: " + readWriteQueue.size()
1505 +
", a job is pending: " + (pendingJob !=
null) +
", BluetoothGatt: "
1506 + (mBluetoothGatt !=
null));
1508 if (mBluetoothGatt ==
null)
1511 boolean skip =
false;
1512 final ReadWriteJob nextJob;
1513 int handle = HANDLE_FOR_RESET;
1515 if (readWriteQueue.isEmpty() || pendingJob !=
null)
1518 nextJob = readWriteQueue.remove();
1521 handle = HANDLE_FOR_MTU_EXCHANGE;
1523 handle = HANDLE_FOR_RSSI_READ;
1525 switch (nextJob.entry.type) {
1526 case Characteristic:
1527 handle = handleForCharacteristic(nextJob.entry.characteristic);
1530 handle = handleForDescriptor(nextJob.entry.descriptor);
1532 case CharacteristicValue:
1533 handle = nextJob.entry.endHandle;
1543 timeoutHandler.removeCallbacksAndMessages(
null);
1544 handleForTimeout.set(modifiedReadWriteHandle(
handle, nextJob.jobType));
1546 switch (nextJob.jobType) {
1548 skip = executeReadJob(nextJob);
1554 skip = executeWriteJob(nextJob);
1557 skip = executeMtuExchange();
1560 skip = executeRemoteRssiRead();
1565 handleForTimeout.set(HANDLE_FOR_RESET);
1567 pendingJob = nextJob;
1568 timeoutHandler.postDelayed(
new TimeoutRunnable(
1569 modifiedReadWriteHandle(
handle, nextJob.jobType)), RUNNABLE_TIMEOUT);
1573 Log.d(TAG,
"Performing queued job, handle: " +
handle +
" " + nextJob.jobType +
" (" +
1574 (nextJob.requestedWriteType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) +
1575 ") ValueKnown: " + nextJob.entry.valueKnown +
" Skipping: " + skip +
1576 " " + nextJob.entry.type);
1579 GattEntry
entry = nextJob.entry;
1589 if (
handle > HANDLE_FOR_RESET) {
1593 final boolean isServiceDiscovery = !
entry.valueKnown;
1595 if (isServiceDiscovery) {
1596 entry.valueKnown =
true;
1597 switch (
entry.type) {
1598 case Characteristic:
1600 nextJob.jobType ==
IoJobType.
Read ?
"Non-readable" :
"Skipped reading of"
1601 +
" characteristic " +
entry.characteristic.getUuid()
1602 +
" for service " +
entry.characteristic.getService().getUuid());
1603 leCharacteristicRead(qtObject,
entry.characteristic.getService().getUuid().toString(),
1604 handle + 1,
entry.characteristic.getUuid().toString(),
1605 entry.characteristic.getProperties(),
null);
1609 nextJob.jobType ==
IoJobType.
Read ?
"Non-readable" :
"Skipped reading of"
1610 +
" descriptor " +
entry.descriptor.getUuid()
1611 +
" for service/char " +
entry.descriptor.getCharacteristic().getService().getUuid()
1612 +
"/" +
entry.descriptor.getCharacteristic().getUuid());
1613 leDescriptorRead(qtObject,
1614 entry.descriptor.getCharacteristic().getService().getUuid().toString(),
1615 entry.descriptor.getCharacteristic().getUuid().toString(),
1616 handle + 1,
entry.descriptor.getUuid().toString(),
1619 case CharacteristicValue:
1623 Log.w(TAG,
"Scheduling of Service Gatt entry for service discovery should never happen.");
1629 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
1630 if (serviceEntry.endHandle ==
handle)
1631 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
1632 }
catch (IndexOutOfBoundsException outOfBounds) {
1633 Log.w(TAG,
"performNextIO(): Unknown service for entry, index: "
1634 +
entry.associatedServiceHandle +
" size: " + entries.size());
1648 leServiceError(qtObject,
handle + 1, errorCode);
1656 private BluetoothGattCharacteristic cloneChararacteristic(BluetoothGattCharacteristic other) {
1658 return mCharacteristicConstructor.newInstance(other.getService(), other.getUuid(),
1659 other.getInstanceId(), other.getProperties(), other.getPermissions());
1660 }
catch (Exception ex) {
1661 Log.w(TAG,
"Cloning characteristic failed!" + ex);
1667 @SuppressWarnings(
"deprecation")
1668 private boolean executeCharacteristicWriteJob(ReadWriteJob nextJob) {
1669 if (mHandler !=
null || mCharacteristicConstructor ==
null) {
1670 if (nextJob.entry.characteristic.getWriteType() != nextJob.requestedWriteType) {
1671 nextJob.entry.characteristic.setWriteType(nextJob.requestedWriteType);
1673 return !nextJob.entry.characteristic.setValue(nextJob.newValue)
1674 || !mBluetoothGatt.writeCharacteristic(nextJob.entry.characteristic);
1676 BluetoothGattCharacteristic orig = nextJob.entry.characteristic;
1677 BluetoothGattCharacteristic tmp = cloneChararacteristic(orig);
1680 tmp.setWriteType(nextJob.requestedWriteType);
1681 return !tmp.setValue(nextJob.newValue) || !mBluetoothGatt.writeCharacteristic(tmp);
1686 @SuppressWarnings(
"deprecation")
1687 private boolean executeDescriptorWriteJob(ReadWriteJob nextJob) {
1688 return !nextJob.entry.descriptor.setValue(nextJob.newValue)
1689 || !mBluetoothGatt.writeDescriptor(nextJob.entry.descriptor);
1693 private boolean executeWriteJob(ReadWriteJob nextJob)
1696 switch (nextJob.entry.type) {
1697 case Characteristic:
1698 if (Build.VERSION.SDK_INT >= 33) {
1699 int writeResult = mBluetoothGatt.writeCharacteristic(
1700 nextJob.entry.characteristic, nextJob.newValue, nextJob.requestedWriteType);
1701 return (writeResult != BluetoothStatusCodes.SUCCESS);
1703 return executeCharacteristicWriteJob(nextJob);
1705 if (nextJob.entry.descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
1723 boolean enableNotifications =
false;
1724 int value = (nextJob.newValue[0] & 0xff);
1727 enableNotifications =
true;
1730 result = mBluetoothGatt.setCharacteristicNotification(
1731 nextJob.entry.descriptor.getCharacteristic(), enableNotifications);
1733 Log.w(TAG,
"Cannot set characteristic notification");
1738 Log.d(TAG,
"Enable notifications: " + enableNotifications);
1741 if (Build.VERSION.SDK_INT >= 33) {
1742 int writeResult = mBluetoothGatt.writeDescriptor(
1743 nextJob.entry.descriptor, nextJob.newValue);
1744 return (writeResult != BluetoothStatusCodes.SUCCESS);
1746 return executeDescriptorWriteJob(nextJob);
1748 case CharacteristicValue:
1755 private boolean executeReadJob(ReadWriteJob nextJob)
1758 switch (nextJob.entry.type) {
1759 case Characteristic:
1761 result = mBluetoothGatt.readCharacteristic(nextJob.entry.characteristic);
1762 }
catch (java.lang.SecurityException se) {
1764 se.printStackTrace();
1772 result = mBluetoothGatt.readDescriptor(nextJob.entry.descriptor);
1773 }
catch (java.lang.SecurityException se) {
1775 se.printStackTrace();
1783 case CharacteristicValue:
1809 int modifiedHandle =
handle;
1812 Log.w(TAG,
"Invalid handle");
1814 modifiedHandle = (modifiedHandle & 0xFFFF);
1818 modifiedHandle = (modifiedHandle | 0x00010000);
1821 modifiedHandle = (modifiedHandle | 0x00020000);
1824 modifiedHandle = HANDLE_FOR_MTU_EXCHANGE;
1827 modifiedHandle = HANDLE_FOR_RSSI_READ;
1831 return modifiedHandle;
1835 synchronized boolean requestConnectionUpdatePriority(
double minimalInterval)
1837 if (mBluetoothGatt ==
null)
1840 int requestPriority = 0;
1841 if (minimalInterval < 30)
1842 requestPriority = 1;
1843 else if (minimalInterval > 100)
1844 requestPriority = 2;
1847 return mBluetoothGatt.requestConnectionPriority(requestPriority);
1848 }
catch (IllegalArgumentException ex) {
1849 Log.w(TAG,
"Connection update priority out of range: " + requestPriority);
1854 native
void leConnectionStateChange(
long qtObject,
int wasErrorTransition,
int newState);
1855 native
void leMtuChanged(
long qtObject,
int mtu);
1856 native
void leRemoteRssiRead(
long qtObject,
int rssi,
boolean success);
1857 native
void leServicesDiscovered(
long qtObject,
int errorCode,
String uuidList);
1858 native
void leServiceDetailDiscoveryFinished(
long qtObject,
final String serviceUuid,
1859 int startHandle,
int endHandle);
1860 native
void leCharacteristicRead(
long qtObject,
String serviceUuid,
1861 int charHandle,
String charUuid,
1863 native
void leDescriptorRead(
long qtObject,
String serviceUuid,
String charUuid,
1864 int descHandle,
String descUuid,
byte[]
data);
1865 native
void leCharacteristicWritten(
long qtObject,
int charHandle,
byte[] newData,
1867 native
void leDescriptorWritten(
long qtObject,
int charHandle,
byte[] newData,
1869 native
void leCharacteristicChanged(
long qtObject,
int charHandle,
byte[] newData);
1870 native
void leServiceError(
long qtObject,
int attributeHandle,
int errorCode);
IOBluetoothDevice * device
std::vector< ObjCStrongReference< CBMutableService > > services
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
static const QString context()
Q_CORE_EXPORT QtJniTypes::Service service()
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
static constexpr QCssKnownValue properties[]
EGLOutputLayerEXT EGLint EGLAttrib value
[3]
GLuint GLfloat GLfloat GLfloat x1
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
[0]
EGLImageKHR EGLint EGLint * handle
QNetworkAccessManager manager
[0]
if(foo.startsWith("("+type+") 0x")) ... QString hello("hello")
[0]