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";
44 private BluetoothAdapter mBluetoothAdapter =
null;
45 private boolean mLeScanRunning =
false;
47 private BluetoothGatt mBluetoothGatt =
null;
48 private HandlerThread mHandlerThread =
null;
49 private Handler mHandler =
null;
50 private Constructor 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)
116 final BluetoothDevice
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
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 =
122 intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
124 if (bondState == BluetoothDevice.BOND_BONDING) {
125 if (pendingJob ==
null
126 || pendingJob.jobType == IoJobType.
Mtu || pendingJob.jobType == IoJobType.
Rssi) {
131 handleForTimeout.set(HANDLE_FOR_RESET);
132 }
else if (previousBondState == BluetoothDevice.BOND_BONDING &&
133 (bondState == BluetoothDevice.BOND_BONDED || bondState == BluetoothDevice.BOND_NONE)) {
134 if (pendingJob ==
null
135 || pendingJob.jobType == IoJobType.
Mtu || pendingJob.jobType == IoJobType.
Rssi) {
139 readWriteQueue.addFirst(pendingJob);
143 }
else if (previousBondState == BluetoothDevice.BOND_BONDED
144 && bondState == BluetoothDevice.BOND_NONE) {
151 device.getClass().getMethod(
"removeBond").invoke(
device);
152 }
catch (Exception ex) {
153 ex.printStackTrace();
158 private class BondStateBroadcastReceiver
extends BroadcastReceiver {
160 public void onReceive(Context context, Intent intent) {
161 handleOnReceive(context, intent);
164 private BroadcastReceiver bondStateBroadcastReceiver =
null;
167 @SuppressWarnings({
"CanBeFinal",
"WeakerAccess"})
169 @SuppressWarnings(
"WeakerAccess")
170 Context qtContext = null;
172 @SuppressWarnings("WeakerAccess")
173 QtBluetoothLE(Context context) {
177 (BluetoothManager)qtContext.getSystemService(Context.BLUETOOTH_SERVICE);
181 mBluetoothAdapter =
manager.getAdapter();
182 if (mBluetoothAdapter ==
null)
185 mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
188 QtBluetoothLE(
final String remoteAddress, Context context) {
190 mRemoteGattAddress = remoteAddress;
200 boolean scanForLeDevice(
final boolean isEnabled) {
201 if (isEnabled == mLeScanRunning)
204 if (mBluetoothLeScanner ==
null) {
205 Log.w(TAG,
"Cannot start LE scan, no bluetooth scanner");
210 Log.d(TAG,
"Attempting to start BTLE scan");
211 ScanSettings.Builder settingsBuilder =
new ScanSettings.Builder();
212 settingsBuilder = settingsBuilder.setScanMode(ScanSettings.SCAN_MODE_BALANCED);
213 ScanSettings
settings = settingsBuilder.build();
215 List<ScanFilter> filterList =
new ArrayList<ScanFilter>();
217 mBluetoothLeScanner.startScan(filterList,
settings, leScanCallback);
218 mLeScanRunning =
true;
220 Log.d(TAG,
"Attempting to stop BTLE scan");
222 mBluetoothLeScanner.stopScan(leScanCallback);
223 }
catch (IllegalStateException isex) {
226 Log.d(TAG,
"Stopping LE scan not possible: " + isex.getMessage());
228 mLeScanRunning =
false;
231 return (mLeScanRunning == isEnabled);
234 private final ScanCallback leScanCallback =
new ScanCallback() {
236 public void onScanResult(
int callbackType, ScanResult
result) {
237 super.onScanResult(callbackType,
result);
238 leScanResult(qtObject,
result.getDevice(),
result.getRssi(),
result.getScanRecord().getBytes());
242 public void onBatchScanResults(List<ScanResult>
results) {
243 super.onBatchScanResults(
results);
250 public void onScanFailed(
int errorCode) {
251 super.onScanFailed(errorCode);
252 Log.d(TAG,
"BTLE device scan failed with " + errorCode);
256 native
void leScanResult(
long qtObject, BluetoothDevice
device,
int rssi,
byte[] scanRecord);
258 private synchronized void handleOnConnectionStateChange(BluetoothGatt gatt,
261 Log.d(TAG,
"Connection state changes to: " +
newState +
", status: " + status
262 +
", qtObject: " + (qtObject != 0));
266 int qLowEnergyController_State = 0;
269 case BluetoothProfile.STATE_DISCONNECTED:
270 if (bondStateBroadcastReceiver !=
null) {
271 qtContext.unregisterReceiver(bondStateBroadcastReceiver);
272 bondStateBroadcastReceiver =
null;
275 qLowEnergyController_State = 0;
280 if (mBluetoothGatt !=
null) {
281 mBluetoothGatt.close();
282 if (mHandler !=
null) {
283 mHandler.getLooper().quitSafely();
287 mBluetoothGatt =
null;
289 case BluetoothProfile.STATE_CONNECTED:
290 if (bondStateBroadcastReceiver ==
null) {
291 bondStateBroadcastReceiver =
new BondStateBroadcastReceiver();
292 qtContext.registerReceiver(bondStateBroadcastReceiver,
293 new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
295 qLowEnergyController_State = 2;
301 case BluetoothGatt.GATT_SUCCESS:
304 case BluetoothGatt.GATT_FAILURE:
308 Log.w(TAG,
"Connection Error: Try to delay connect() call after previous activity");
314 Log.w(TAG,
"The remote host closed the connection");
322 Log.w(TAG,
"Unhandled error code on connectionStateChanged: "
327 leConnectionStateChange(qtObject, errorCode, qLowEnergyController_State);
330 private synchronized void handleOnServicesDiscovered(BluetoothGatt gatt,
int status) {
333 StringBuilder builder =
new StringBuilder();
335 case BluetoothGatt.GATT_SUCCESS:
337 final List<BluetoothGattService>
services = mBluetoothGatt.getServices();
338 for (BluetoothGattService service:
services) {
339 builder.append(
service.getUuid().toString()).append(
" ");
343 Log.w(TAG,
"Unhandled error code on onServicesDiscovered: " + status);
344 errorCode = status;
break;
346 leServicesDiscovered(qtObject, errorCode, builder.toString());
347 if (status == BluetoothGatt.GATT_SUCCESS)
348 scheduleMtuExchange();
351 private synchronized void handleOnCharacteristicRead(BluetoothGatt gatt,
352 BluetoothGattCharacteristic characteristic,
356 int foundHandle = handleForCharacteristic(characteristic);
357 if (foundHandle == -1 || foundHandle >= entries.size() ) {
358 Log.w(TAG,
"Cannot find characteristic read request for read notification - handle: " +
359 foundHandle +
" size: " + entries.size());
368 boolean requestTimedOut = !handleForTimeout.compareAndSet(
369 modifiedReadWriteHandle(foundHandle, IoJobType.Read),
371 if (requestTimedOut) {
372 Log.w(TAG,
"Late char read reply after timeout was hit for handle " + foundHandle);
378 GattEntry
entry = entries.get(foundHandle);
379 final boolean isServiceDiscoveryRun = !
entry.valueKnown;
380 entry.valueKnown =
true;
382 if (status == BluetoothGatt.GATT_SUCCESS) {
385 leCharacteristicRead(qtObject,
386 characteristic.getService().getUuid().toString(),
387 foundHandle + 1, characteristic.getUuid().toString(),
388 characteristic.getProperties(),
value);
390 if (isServiceDiscoveryRun) {
391 Log.w(TAG,
"onCharacteristicRead during discovery error: " + status);
393 Log.d(TAG,
"Non-readable characteristic " + characteristic.getUuid() +
394 " for service " + characteristic.getService().getUuid());
395 leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(),
396 foundHandle + 1, characteristic.getUuid().toString(),
397 characteristic.getProperties(),
value);
400 final int characteristicReadError = 5;
401 leServiceError(qtObject, foundHandle + 1, characteristicReadError);
405 if (isServiceDiscoveryRun) {
408 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
409 if (serviceEntry.endHandle == foundHandle)
410 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
419 private synchronized void handleOnCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
420 android.bluetooth.BluetoothGattCharacteristic characteristic,
423 int handle = handleForCharacteristic(characteristic);
425 Log.w(TAG,
"onCharacteristicChanged: cannot find handle");
429 leCharacteristicChanged(qtObject,
handle+1,
value);
432 private synchronized void handleOnCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
433 android.bluetooth.BluetoothGattCharacteristic characteristic,
436 if (status != BluetoothGatt.GATT_SUCCESS)
437 Log.w(TAG,
"onCharacteristicWrite: error " + status);
439 int handle = handleForCharacteristic(characteristic);
441 Log.w(TAG,
"onCharacteristicWrite: cannot find handle");
445 boolean requestTimedOut = !handleForTimeout.compareAndSet(
446 modifiedReadWriteHandle(
handle, IoJobType.Write),
448 if (requestTimedOut) {
449 Log.w(TAG,
"Late char write reply after timeout was hit for handle " +
handle);
458 case BluetoothGatt.GATT_SUCCESS:
467 value = pendingJob.newValue;
470 leCharacteristicWritten(qtObject,
handle+1,
value, errorCode);
474 private synchronized void handleOnDescriptorRead(android.bluetooth.BluetoothGatt gatt,
475 android.bluetooth.BluetoothGattDescriptor descriptor,
476 int status,
byte[] newValue)
478 int foundHandle = handleForDescriptor(descriptor);
479 if (foundHandle == -1 || foundHandle >= entries.size() ) {
480 Log.w(TAG,
"Cannot find descriptor read request for read notification - handle: " +
481 foundHandle +
" size: " + entries.size());
490 boolean requestTimedOut = !handleForTimeout.compareAndSet(
491 modifiedReadWriteHandle(foundHandle, IoJobType.Read),
493 if (requestTimedOut) {
494 Log.w(TAG,
"Late descriptor read reply after timeout was hit for handle " +
501 GattEntry
entry = entries.get(foundHandle);
502 final boolean isServiceDiscoveryRun = !
entry.valueKnown;
503 entry.valueKnown =
true;
505 if (status == BluetoothGatt.GATT_SUCCESS) {
507 leDescriptorRead(qtObject,
508 descriptor.getCharacteristic().getService().getUuid().toString(),
509 descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
510 descriptor.getUuid().toString(), newValue);
512 if (isServiceDiscoveryRun) {
515 Log.w(TAG,
"onDescriptorRead during discovery error: " + status);
516 Log.d(TAG,
"Non-readable descriptor " + descriptor.getUuid() +
517 " for characteristic " + descriptor.getCharacteristic().getUuid() +
518 " for service " + descriptor.getCharacteristic().getService().getUuid());
519 leDescriptorRead(qtObject,
520 descriptor.getCharacteristic().getService().getUuid().toString(),
521 descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
522 descriptor.getUuid().toString(), newValue);
525 final int descriptorReadError = 6;
526 leServiceError(qtObject, foundHandle + 1, descriptorReadError);
531 if (isServiceDiscoveryRun) {
533 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
534 if (serviceEntry.endHandle == foundHandle) {
535 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
544 if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
548 if ((
value & 0x03) > 0) {
549 Log.d(TAG,
"Found descriptor with automatic notifications.");
550 mBluetoothGatt.setCharacteristicNotification(
551 descriptor.getCharacteristic(),
true);
562 private synchronized void handleOnDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
563 android.bluetooth.BluetoothGattDescriptor descriptor,
566 if (status != BluetoothGatt.GATT_SUCCESS)
567 Log.w(TAG,
"onDescriptorWrite: error " + status);
569 int handle = handleForDescriptor(descriptor);
571 boolean requestTimedOut = !handleForTimeout.compareAndSet(
572 modifiedReadWriteHandle(
handle, IoJobType.Write),
574 if (requestTimedOut) {
575 Log.w(TAG,
"Late descriptor write reply after timeout was hit for handle " +
585 case BluetoothGatt.GATT_SUCCESS:
586 errorCode = 0;
break;
588 errorCode = 3;
break;
591 byte[]
value = pendingJob.newValue;
594 leDescriptorWritten(qtObject,
handle+1,
value, errorCode);
598 private synchronized void handleOnMtuChanged(android.bluetooth.BluetoothGatt gatt,
601 int previousMtu = mSupportedMtu;
602 if (status == BluetoothGatt.GATT_SUCCESS) {
603 Log.w(TAG,
"MTU changed to " + mtu);
606 Log.w(TAG,
"MTU change error " + status +
". New MTU " + mtu);
607 mSupportedMtu = DEFAULT_MTU;
609 if (previousMtu != mSupportedMtu)
610 leMtuChanged(qtObject, mSupportedMtu);
612 boolean requestTimedOut = !handleForTimeout.compareAndSet(
613 modifiedReadWriteHandle(HANDLE_FOR_MTU_EXCHANGE, IoJobType.Mtu), HANDLE_FOR_RESET);
614 if (requestTimedOut) {
615 Log.w(TAG,
"Late mtu reply after timeout was hit");
626 private synchronized void handleOnReadRemoteRssi(android.bluetooth.BluetoothGatt gatt,
627 int rssi,
int status)
629 Log.d(TAG,
"RSSI read callback, rssi: " +
rssi +
", status: " + status);
630 leRemoteRssiRead(qtObject,
rssi, status == BluetoothGatt.GATT_SUCCESS);
632 boolean requestTimedOut = !handleForTimeout.compareAndSet(
633 modifiedReadWriteHandle(HANDLE_FOR_RSSI_READ, IoJobType.Rssi), HANDLE_FOR_RESET);
634 if (requestTimedOut) {
635 Log.w(TAG,
"Late RSSI read reply after timeout was hit");
648 private final BluetoothGattCallback gattCallback =
new BluetoothGattCallback() {
650 public void onConnectionStateChange(BluetoothGatt gatt,
int status,
int newState) {
651 super.onConnectionStateChange(gatt, status,
newState);
652 handleOnConnectionStateChange(gatt, status,
newState);
656 public void onServicesDiscovered(BluetoothGatt gatt,
int status) {
657 super.onServicesDiscovered(gatt, status);
658 handleOnServicesDiscovered(gatt, status);
664 public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
665 android.bluetooth.BluetoothGattCharacteristic characteristic,
668 super.onCharacteristicRead(gatt, characteristic, status);
669 handleOnCharacteristicRead(gatt, characteristic, characteristic.getValue(), status);
674 public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
675 android.bluetooth.BluetoothGattCharacteristic characteristic,
681 handleOnCharacteristicRead(gatt, characteristic,
value, status);
685 public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
686 android.bluetooth.BluetoothGattCharacteristic characteristic,
689 super.onCharacteristicWrite(gatt, characteristic, status);
690 handleOnCharacteristicWrite(gatt, characteristic, status);
695 public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
696 android.bluetooth.BluetoothGattCharacteristic characteristic)
698 super.onCharacteristicChanged(gatt, characteristic);
699 handleOnCharacteristicChanged(gatt, characteristic, characteristic.getValue());
704 public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
705 android.bluetooth.BluetoothGattCharacteristic characteristic,
710 handleOnCharacteristicChanged(gatt, characteristic,
value);
715 public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
716 android.bluetooth.BluetoothGattDescriptor descriptor,
719 super.onDescriptorRead(gatt, descriptor, status);
720 handleOnDescriptorRead(gatt, descriptor, status, descriptor.getValue());
725 public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
726 android.bluetooth.BluetoothGattDescriptor descriptor,
732 handleOnDescriptorRead(gatt, descriptor, status,
value);
736 public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
737 android.bluetooth.BluetoothGattDescriptor descriptor,
740 super.onDescriptorWrite(gatt, descriptor, status);
741 handleOnDescriptorWrite(gatt, descriptor, status);
751 public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt,
int rssi,
int status)
753 super.onReadRemoteRssi(gatt,
rssi, status);
754 handleOnReadRemoteRssi(gatt,
rssi, status);
758 public void onMtuChanged(android.bluetooth.BluetoothGatt gatt,
int mtu,
int status)
760 super.onMtuChanged(gatt, mtu, status);
761 handleOnMtuChanged(gatt, mtu, status);
766 synchronized int mtu() {
767 if (mSupportedMtu == -1) {
770 return mSupportedMtu;
775 synchronized boolean readRemoteRssi() {
776 if (mBluetoothGatt ==
null)
782 ReadWriteJob newJob =
new ReadWriteJob();
783 newJob.jobType = IoJobType.Rssi;
786 if (!readWriteQueue.add(newJob)) {
787 Log.w(TAG,
"Cannot add remote RSSI read to queue" );
791 performNextIOThreaded();
796 synchronized boolean connect() {
797 BluetoothDevice mRemoteGattDevice;
799 if (mBluetoothAdapter ==
null) {
800 Log.w(TAG,
"Cannot connect, no bluetooth adapter");
805 mRemoteGattDevice = mBluetoothAdapter.getRemoteDevice(mRemoteGattAddress);
806 }
catch (IllegalArgumentException ex) {
807 Log.w(TAG,
"Remote address is not valid: " + mRemoteGattAddress);
814 if (Build.VERSION.SDK_INT >= 27) {
815 HandlerThread handlerThread =
new HandlerThread(
"QtBluetoothLEHandlerThread");
816 handlerThread.start();
817 mHandler =
new Handler(handlerThread.getLooper());
820 args[0] = android.content.Context.class;
821 args[1] =
boolean.class;
822 args[2] = android.bluetooth.BluetoothGattCallback.class;
825 args[5] = android.os.Handler.class;
828 Method connectMethod = mRemoteGattDevice.getClass().getDeclaredMethod(
"connectGatt",
args);
829 if (connectMethod !=
null) {
830 mBluetoothGatt = (BluetoothGatt) connectMethod.invoke(mRemoteGattDevice, qtContext,
false,
831 gattCallback, 2 , 1 , mHandler);
832 Log.w(TAG,
"Using Android v26 BluetoothDevice.connectGatt()");
834 }
catch (Exception ex) {
835 Log.w(TAG,
"connectGatt() v26 not available");
836 ex.printStackTrace();
839 if (mBluetoothGatt ==
null) {
840 mHandler.getLooper().quitSafely();
845 if (mBluetoothGatt ==
null) {
850 constr_args[0] = android.bluetooth.BluetoothGattService.class;
851 constr_args[1] = java.util.UUID.class;
852 constr_args[2] =
int.class;
853 constr_args[3] =
int.class;
854 constr_args[4] =
int.class;
855 mCharacteristicConstructor = BluetoothGattCharacteristic.class.getDeclaredConstructor(constr_args);
856 mCharacteristicConstructor.setAccessible(
true);
857 }
catch (NoSuchMethodException ex) {
858 Log.w(TAG,
"Unable get characteristic constructor. Buffer race condition are possible");
867 mRemoteGattDevice.connectGatt(qtContext,
false,
869 }
catch (IllegalArgumentException ex) {
870 Log.w(TAG,
"Gatt connection failed");
871 ex.printStackTrace();
874 return mBluetoothGatt !=
null;
878 synchronized void disconnect() {
879 if (mBluetoothGatt ==
null)
882 mBluetoothGatt.disconnect();
886 synchronized boolean discoverServices()
888 return mBluetoothGatt !=
null && mBluetoothGatt.discoverServices();
895 private class GattEntry
898 boolean valueKnown =
false;
899 BluetoothGattService service =
null;
900 BluetoothGattCharacteristic characteristic =
null;
901 BluetoothGattDescriptor descriptor =
null;
910 int associatedServiceHandle;
921 private class ReadWriteJob
925 int requestedWriteType;
930 private final Hashtable<UUID, List<Integer>> uuidToEntry =
new Hashtable<UUID, List<Integer>>(100);
932 private final ArrayList<GattEntry> entries =
new ArrayList<GattEntry>(100);
934 private final LinkedList<Integer> servicesToBeDiscovered =
new LinkedList<Integer>();
937 private final LinkedList<ReadWriteJob> readWriteQueue =
new LinkedList<ReadWriteJob>();
938 private ReadWriteJob pendingJob;
946 private int handleForCharacteristic(BluetoothGattCharacteristic characteristic)
948 if (characteristic ==
null)
951 List<Integer> handles = uuidToEntry.get(characteristic.getService().getUuid());
952 if (handles ==
null || handles.isEmpty())
956 int serviceHandle = handles.get(0);
960 for (
int i = serviceHandle+1;
i < entries.size();
i++) {
965 switch (
entry.type) {
967 case CharacteristicValue:
972 if (
entry.characteristic == characteristic)
977 }
catch (IndexOutOfBoundsException ex) { }
987 private int handleForDescriptor(BluetoothGattDescriptor descriptor)
989 if (descriptor ==
null)
992 List<Integer> handles = uuidToEntry.get(descriptor.getCharacteristic().getService().getUuid());
993 if (handles ==
null || handles.isEmpty())
997 int serviceHandle = handles.get(0);
1001 for (
int i = serviceHandle+1;
i < entries.size();
i++) {
1006 switch (
entry.type) {
1007 case Characteristic:
1008 case CharacteristicValue:
1013 if (
entry.descriptor == descriptor)
1018 }
catch (IndexOutOfBoundsException ignored) { }
1023 private void populateHandles()
1028 GattEntry
entry =
null;
1029 List<BluetoothGattService>
services = mBluetoothGatt.getServices();
1030 for (BluetoothGattService service:
services) {
1031 GattEntry serviceEntry =
new GattEntry();
1032 serviceEntry.type = GattEntryType.Service;
1033 serviceEntry.service =
service;
1034 entries.add(serviceEntry);
1037 int serviceHandle = entries.size() - 1;
1039 serviceEntry.associatedServiceHandle = serviceHandle;
1042 List<Integer> old = uuidToEntry.get(
service.getUuid());
1044 old =
new ArrayList<Integer>();
1045 old.add(entries.size()-1);
1046 uuidToEntry.put(
service.getUuid(), old);
1049 List<BluetoothGattCharacteristic> charList =
service.getCharacteristics();
1050 for (BluetoothGattCharacteristic characteristic: charList) {
1051 entry =
new GattEntry();
1052 entry.type = GattEntryType.Characteristic;
1053 entry.characteristic = characteristic;
1054 entry.associatedServiceHandle = serviceHandle;
1059 entry =
new GattEntry();
1060 entry.type = GattEntryType.CharacteristicValue;
1061 entry.associatedServiceHandle = serviceHandle;
1062 entry.endHandle = entries.size();
1066 List<BluetoothGattDescriptor> descList = characteristic.getDescriptors();
1067 for (BluetoothGattDescriptor desc: descList) {
1068 entry =
new GattEntry();
1069 entry.type = GattEntryType.Descriptor;
1070 entry.descriptor =
desc;
1071 entry.associatedServiceHandle = serviceHandle;
1078 serviceEntry.endHandle = entries.size() - 1;
1081 entries.trimToSize();
1084 private void resetData()
1086 uuidToEntry.clear();
1088 servicesToBeDiscovered.clear();
1092 handleForTimeout.set(HANDLE_FOR_RESET);
1094 readWriteQueue.clear();
1099 synchronized boolean discoverServiceDetails(String serviceUuid,
boolean fullDiscovery)
1101 Log.d(TAG,
"Discover service details for: " + serviceUuid +
", fullDiscovery: "
1102 + fullDiscovery +
", BluetoothGatt: " + (mBluetoothGatt !=
null));
1104 if (mBluetoothGatt ==
null)
1107 if (entries.isEmpty())
1113 UUID
service = UUID.fromString(serviceUuid);
1114 List<Integer> handles = uuidToEntry.get(service);
1115 if (handles ==
null || handles.isEmpty()) {
1116 Log.w(TAG,
"Unknown service uuid for current device: " +
service.toString());
1121 serviceHandle = handles.get(0);
1122 entry = entries.get(serviceHandle);
1123 if (
entry ==
null) {
1124 Log.w(TAG,
"Service with UUID " +
service.toString() +
" not found");
1127 }
catch (IllegalArgumentException ex) {
1129 Log.w(TAG,
"Cannot parse given UUID");
1133 if (
entry.type != GattEntryType.Service) {
1134 Log.w(TAG,
"Given UUID is not a service UUID: " + serviceUuid);
1139 if (
entry.valueKnown || servicesToBeDiscovered.contains(serviceHandle)) {
1140 Log.w(TAG,
"Service already known or to be discovered");
1144 servicesToBeDiscovered.add(serviceHandle);
1145 scheduleServiceDetailDiscovery(serviceHandle, fullDiscovery);
1146 performNextIOThreaded();
1147 }
catch (Exception ex) {
1148 ex.printStackTrace();
1159 synchronized String includedServices(String serviceUuid)
1161 if (mBluetoothGatt ==
null)
1166 uuid = UUID.fromString(serviceUuid);
1167 }
catch (Exception ex) {
1168 ex.printStackTrace();
1173 BluetoothGattService
service = mBluetoothGatt.getService(uuid);
1174 if (service ==
null)
1177 final List<BluetoothGattService> includes =
service.getIncludedServices();
1178 if (includes.isEmpty())
1181 StringBuilder builder =
new StringBuilder();
1182 for (BluetoothGattService includedService: includes) {
1183 builder.append(includedService.getUuid().toString()).append(
" ");
1186 return builder.toString();
1189 private synchronized void finishCurrentServiceDiscovery(
int handleDiscoveredService)
1191 Log.w(TAG,
"Finished current discovery for service handle " + handleDiscoveredService);
1192 GattEntry discoveredService = entries.get(handleDiscoveredService);
1193 discoveredService.valueKnown =
true;
1195 servicesToBeDiscovered.removeFirst();
1196 }
catch (NoSuchElementException ex) {
1197 Log.w(TAG,
"Expected queued service but didn't find any");
1200 leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(),
1201 handleDiscoveredService + 1, discoveredService.endHandle + 1);
1206 private boolean executeMtuExchange()
1208 if (mBluetoothGatt.requestMtu(MAX_MTU)) {
1209 Log.w(TAG,
"MTU change initiated");
1212 Log.w(TAG,
"MTU change request failed");
1215 Log.w(TAG,
"Assuming default MTU value of 23 bytes");
1216 mSupportedMtu = DEFAULT_MTU;
1220 private boolean executeRemoteRssiRead()
1222 if (mBluetoothGatt.readRemoteRssi()) {
1223 Log.d(TAG,
"RSSI read initiated");
1226 Log.w(TAG,
"Initiating remote RSSI read failed");
1227 leRemoteRssiRead(qtObject, 0,
false);
1235 private void scheduleMtuExchange() {
1236 ReadWriteJob newJob =
new ReadWriteJob();
1237 newJob.jobType = IoJobType.Mtu;
1238 newJob.entry =
null;
1240 readWriteQueue.add(newJob);
1252 private void scheduleServiceDetailDiscovery(
int serviceHandle,
boolean fullDiscovery)
1254 GattEntry serviceEntry = entries.get(serviceHandle);
1255 final int endHandle = serviceEntry.endHandle;
1257 if (serviceHandle == endHandle) {
1258 Log.w(TAG,
"scheduleServiceDetailDiscovery: service is empty; nothing to discover");
1259 finishCurrentServiceDiscovery(serviceHandle);
1264 for (
int i = serviceHandle + 1;
i <= endHandle;
i++) {
1265 GattEntry
entry = entries.get(
i);
1267 if (
entry.type == GattEntryType.Service) {
1269 Log.w(TAG,
"scheduleServiceDetailDiscovery: wrong endHandle");
1273 ReadWriteJob newJob =
new ReadWriteJob();
1274 newJob.entry =
entry;
1275 if (fullDiscovery) {
1276 newJob.jobType = IoJobType.Read;
1278 newJob.jobType = IoJobType.SkippedRead;
1281 final boolean result = readWriteQueue.add(newJob);
1283 Log.w(TAG,
"Cannot add service discovery job for " + serviceEntry.service.getUuid()
1284 +
" on item " +
entry.type);
1293 synchronized boolean writeCharacteristic(
int charHandle,
byte[] newValue,
1296 if (mBluetoothGatt ==
null)
1301 entry = entries.get(charHandle-1);
1302 }
catch (IndexOutOfBoundsException ex) {
1303 ex.printStackTrace();
1307 ReadWriteJob newJob =
new ReadWriteJob();
1308 newJob.newValue = newValue;
1309 newJob.entry =
entry;
1310 newJob.jobType = IoJobType.Write;
1313 switch (writeMode) {
1315 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
1318 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_SIGNED;
1321 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
1326 result = readWriteQueue.add(newJob);
1329 Log.w(TAG,
"Cannot add characteristic write request for " + charHandle +
" to queue" );
1333 performNextIOThreaded();
1342 synchronized boolean writeDescriptor(
int descHandle,
byte[] newValue)
1344 if (mBluetoothGatt ==
null)
1349 entry = entries.get(descHandle-1);
1350 }
catch (IndexOutOfBoundsException ex) {
1351 ex.printStackTrace();
1355 ReadWriteJob newJob =
new ReadWriteJob();
1356 newJob.newValue = newValue;
1357 newJob.entry =
entry;
1358 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
1359 newJob.jobType = IoJobType.Write;
1362 result = readWriteQueue.add(newJob);
1365 Log.w(TAG,
"Cannot add descriptor write request for " + descHandle +
" to queue" );
1369 performNextIOThreaded();
1378 synchronized boolean readCharacteristic(
int charHandle)
1380 if (mBluetoothGatt ==
null)
1385 entry = entries.get(charHandle-1);
1386 }
catch (IndexOutOfBoundsException ex) {
1387 ex.printStackTrace();
1391 ReadWriteJob newJob =
new ReadWriteJob();
1392 newJob.entry =
entry;
1393 newJob.jobType = IoJobType.Read;
1396 result = readWriteQueue.add(newJob);
1399 Log.w(TAG,
"Cannot add characteristic read request for " + charHandle +
" to queue" );
1403 performNextIOThreaded();
1408 synchronized boolean readDescriptor(
int descHandle)
1410 if (mBluetoothGatt ==
null)
1415 entry = entries.get(descHandle-1);
1416 }
catch (IndexOutOfBoundsException ex) {
1417 ex.printStackTrace();
1421 ReadWriteJob newJob =
new ReadWriteJob();
1422 newJob.entry =
entry;
1423 newJob.jobType = IoJobType.Read;
1426 result = readWriteQueue.add(newJob);
1429 Log.w(TAG,
"Cannot add descriptor read request for " + descHandle +
" to queue" );
1433 performNextIOThreaded();
1440 private synchronized void interruptCurrentIO(
int handle)
1445 performNextIOThreaded();
1447 if (
handle == HANDLE_FOR_MTU_EXCHANGE ||
handle == HANDLE_FOR_RSSI_READ)
1454 if (
entry.valueKnown)
1456 entry.valueKnown =
true;
1458 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
1459 if (serviceEntry !=
null && serviceEntry.endHandle ==
handle)
1460 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
1461 }
catch (IndexOutOfBoundsException outOfBounds) {
1462 Log.w(TAG,
"interruptCurrentIO(): Unknown gatt entry, index: "
1463 +
handle +
" size: " + entries.size());
1471 private void performNextIOThreaded()
1473 if (mHandler !=
null) {
1474 mHandler.post(
new Runnable() {
1490 private synchronized void performNextIO()
1492 Log.d(TAG,
"Perform next BTLE IO, job queue size: " + readWriteQueue.size()
1493 +
", a job is pending: " + (pendingJob !=
null) +
", BluetoothGatt: "
1494 + (mBluetoothGatt !=
null));
1496 if (mBluetoothGatt ==
null)
1499 boolean skip =
false;
1500 final ReadWriteJob nextJob;
1501 int handle = HANDLE_FOR_RESET;
1503 if (readWriteQueue.isEmpty() || pendingJob !=
null)
1506 nextJob = readWriteQueue.remove();
1508 if (nextJob.jobType == IoJobType.Mtu) {
1509 handle = HANDLE_FOR_MTU_EXCHANGE;
1510 }
else if (nextJob.jobType == IoJobType.Rssi) {
1511 handle = HANDLE_FOR_RSSI_READ;
1513 switch (nextJob.entry.type) {
1514 case Characteristic:
1515 handle = handleForCharacteristic(nextJob.entry.characteristic);
1518 handle = handleForDescriptor(nextJob.entry.descriptor);
1520 case CharacteristicValue:
1521 handle = nextJob.entry.endHandle;
1531 handleForTimeout.set(modifiedReadWriteHandle(
handle, nextJob.jobType));
1533 switch (nextJob.jobType) {
1535 skip = executeReadJob(nextJob);
1541 skip = executeWriteJob(nextJob);
1544 skip = executeMtuExchange();
1546 skip = executeRemoteRssiRead();
1551 handleForTimeout.set(HANDLE_FOR_RESET);
1553 pendingJob = nextJob;
1555 modifiedReadWriteHandle(
handle, nextJob.jobType)), RUNNABLE_TIMEOUT);
1558 if (nextJob.jobType != IoJobType.Mtu && nextJob.jobType != IoJobType.Rssi) {
1559 Log.d(TAG,
"Performing queued job, handle: " +
handle +
" " + nextJob.jobType +
" (" +
1560 (nextJob.requestedWriteType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) +
1561 ") ValueKnown: " + nextJob.entry.valueKnown +
" Skipping: " + skip +
1562 " " + nextJob.entry.type);
1565 GattEntry
entry = nextJob.entry;
1575 if (
handle > HANDLE_FOR_RESET) {
1579 final boolean isServiceDiscovery = !
entry.valueKnown;
1581 if (isServiceDiscovery) {
1582 entry.valueKnown =
true;
1583 switch (
entry.type) {
1584 case Characteristic:
1586 nextJob.jobType == IoJobType.Read ?
"Non-readable" :
"Skipped reading of"
1587 +
" characteristic " +
entry.characteristic.getUuid()
1588 +
" for service " +
entry.characteristic.getService().getUuid());
1589 leCharacteristicRead(qtObject,
entry.characteristic.getService().getUuid().toString(),
1590 handle + 1,
entry.characteristic.getUuid().toString(),
1591 entry.characteristic.getProperties(),
null);
1595 nextJob.jobType == IoJobType.Read ?
"Non-readable" :
"Skipped reading of"
1596 +
" descriptor " +
entry.descriptor.getUuid()
1597 +
" for service/char " +
entry.descriptor.getCharacteristic().getService().getUuid()
1598 +
"/" +
entry.descriptor.getCharacteristic().getUuid());
1599 leDescriptorRead(qtObject,
1600 entry.descriptor.getCharacteristic().getService().getUuid().toString(),
1601 entry.descriptor.getCharacteristic().getUuid().toString(),
1602 handle + 1,
entry.descriptor.getUuid().toString(),
1605 case CharacteristicValue:
1609 Log.w(TAG,
"Scheduling of Service Gatt entry for service discovery should never happen.");
1615 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
1616 if (serviceEntry.endHandle ==
handle)
1617 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
1618 }
catch (IndexOutOfBoundsException outOfBounds) {
1619 Log.w(TAG,
"performNextIO(): Unknown service for entry, index: "
1620 +
entry.associatedServiceHandle +
" size: " + entries.size());
1626 if (nextJob.jobType == IoJobType.Read) {
1627 errorCode = (entry.type == GattEntryType.Characteristic) ?
1630 errorCode = (entry.type == GattEntryType.Characteristic) ?
1634 leServiceError(qtObject,
handle + 1, errorCode);
1642 private BluetoothGattCharacteristic cloneChararacteristic(BluetoothGattCharacteristic
other) {
1644 return (BluetoothGattCharacteristic) mCharacteristicConstructor.newInstance(
other.getService(),
1646 }
catch (Exception ex) {
1647 Log.w(TAG,
"Cloning characteristic failed!" + ex);
1653 private boolean executeWriteJob(ReadWriteJob nextJob)
1656 switch (nextJob.entry.type) {
1657 case Characteristic:
1658 if (Build.VERSION.SDK_INT >= 33) {
1659 int writeResult = mBluetoothGatt.writeCharacteristic(
1660 nextJob.entry.characteristic, nextJob.newValue, nextJob.requestedWriteType);
1661 return (writeResult != BluetoothStatusCodes.SUCCESS);
1663 if (mHandler !=
null || mCharacteristicConstructor ==
null) {
1664 if (nextJob.entry.characteristic.getWriteType() != nextJob.requestedWriteType) {
1665 nextJob.entry.characteristic.setWriteType(nextJob.requestedWriteType);
1667 result = nextJob.entry.characteristic.setValue(nextJob.newValue);
1668 return !
result || !mBluetoothGatt.writeCharacteristic(nextJob.entry.characteristic);
1670 BluetoothGattCharacteristic orig = nextJob.entry.characteristic;
1671 BluetoothGattCharacteristic tmp = cloneChararacteristic(orig);
1674 tmp.setWriteType(nextJob.requestedWriteType);
1675 return !tmp.setValue(nextJob.newValue) || !mBluetoothGatt.writeCharacteristic(tmp);
1678 if (nextJob.entry.descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
1696 boolean enableNotifications =
false;
1697 int value = (nextJob.newValue[0] & 0xff);
1700 enableNotifications =
true;
1703 result = mBluetoothGatt.setCharacteristicNotification(
1704 nextJob.entry.descriptor.getCharacteristic(), enableNotifications);
1706 Log.w(TAG,
"Cannot set characteristic notification");
1711 Log.d(TAG,
"Enable notifications: " + enableNotifications);
1714 if (Build.VERSION.SDK_INT >= 33) {
1715 int writeResult = mBluetoothGatt.writeDescriptor(
1716 nextJob.entry.descriptor, nextJob.newValue);
1717 return (writeResult != BluetoothStatusCodes.SUCCESS);
1719 result = nextJob.entry.descriptor.setValue(nextJob.newValue);
1720 if (!
result || !mBluetoothGatt.writeDescriptor(nextJob.entry.descriptor))
1725 case CharacteristicValue:
1732 private boolean executeReadJob(ReadWriteJob nextJob)
1735 switch (nextJob.entry.type) {
1736 case Characteristic:
1738 result = mBluetoothGatt.readCharacteristic(nextJob.entry.characteristic);
1739 }
catch (java.lang.SecurityException se) {
1741 se.printStackTrace();
1749 result = mBluetoothGatt.readDescriptor(nextJob.entry.descriptor);
1750 }
catch (java.lang.SecurityException se) {
1752 se.printStackTrace();
1760 case CharacteristicValue:
1784 private int modifiedReadWriteHandle(
int handle, IoJobType
type)
1786 int modifiedHandle =
handle;
1789 Log.w(TAG,
"Invalid handle");
1791 modifiedHandle = (modifiedHandle & 0xFFFF);
1795 modifiedHandle = (modifiedHandle | 0x00010000);
1798 modifiedHandle = (modifiedHandle | 0x00020000);
1801 modifiedHandle = HANDLE_FOR_MTU_EXCHANGE;
1804 modifiedHandle = HANDLE_FOR_RSSI_READ;
1808 return modifiedHandle;
1812 synchronized boolean requestConnectionUpdatePriority(
double minimalInterval)
1814 if (mBluetoothGatt ==
null)
1817 int requestPriority = 0;
1818 if (minimalInterval < 30)
1819 requestPriority = 1;
1820 else if (minimalInterval > 100)
1821 requestPriority = 2;
1824 return mBluetoothGatt.requestConnectionPriority(requestPriority);
1825 }
catch (IllegalArgumentException ex) {
1826 Log.w(TAG,
"Connection update priority out of range: " + requestPriority);
1831 native
void leConnectionStateChange(
long qtObject,
int wasErrorTransition,
int newState);
1832 native
void leMtuChanged(
long qtObject,
int mtu);
1833 native
void leRemoteRssiRead(
long qtObject,
int rssi,
boolean success);
1834 native
void leServicesDiscovered(
long qtObject,
int errorCode, String uuidList);
1835 native
void leServiceDetailDiscoveryFinished(
long qtObject,
final String serviceUuid,
1836 int startHandle,
int endHandle);
1837 native
void leCharacteristicRead(
long qtObject, String serviceUuid,
1838 int charHandle, String charUuid,
1840 native
void leDescriptorRead(
long qtObject, String serviceUuid, String charUuid,
1841 int descHandle, String descUuid,
byte[]
data);
1842 native
void leCharacteristicWritten(
long qtObject,
int charHandle,
byte[] newData,
1844 native
void leDescriptorWritten(
long qtObject,
int charHandle,
byte[] newData,
1846 native
void leCharacteristicChanged(
long qtObject,
int charHandle,
byte[] newData);
1847 native
void leServiceError(
long qtObject,
int attributeHandle,
int errorCode);
id< QT_MANGLE_NAMESPACE(GCDTimerDelegate)> timeoutHandler
IOBluetoothDevice * device
std::vector< ObjCStrongReference< CBMutableService > > services
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
Q_CORE_EXPORT QtJniTypes::Service service()
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
static const QCssKnownValue properties[NumProperties - 1]
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
GLuint64 GLenum void * handle
GLuint GLfloat GLfloat GLfloat x1
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
static QByteArray getDevice(const QString &rootPath)
QSettings settings("MySoft", "Star Runner")
[0]
QNetworkAccessManager manager