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 public 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
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
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")
177 (BluetoothManager)qtContext.getSystemService(Context.BLUETOOTH_SERVICE);
181 mBluetoothAdapter =
manager.getAdapter();
182 if (mBluetoothAdapter ==
null)
185 mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
190 mRemoteGattAddress = remoteAddress;
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);
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);
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: "
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;
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) {
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());
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");
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;
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) {
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());
520 descriptor.getCharacteristic().getService().getUuid().toString(),
521 descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
522 descriptor.getUuid().toString(), newValue);
525 final int descriptorReadError = 6;
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;
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)
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);
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);
655 public void onServicesDiscovered(BluetoothGatt gatt,
int status) {
656 super.onServicesDiscovered(gatt, status);
657 handleOnServicesDiscovered(gatt, status);
662 public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
663 android.bluetooth.BluetoothGattCharacteristic characteristic,
666 super.onCharacteristicRead(gatt, characteristic, status);
667 handleOnCharacteristicRead(gatt, characteristic, characteristic.getValue(), status);
671 public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
672 android.bluetooth.BluetoothGattCharacteristic characteristic,
678 handleOnCharacteristicRead(gatt, characteristic,
value, status);
681 public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
682 android.bluetooth.BluetoothGattCharacteristic characteristic,
685 super.onCharacteristicWrite(gatt, characteristic, status);
686 handleOnCharacteristicWrite(gatt, characteristic, status);
690 public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
691 android.bluetooth.BluetoothGattCharacteristic characteristic)
693 super.onCharacteristicChanged(gatt, characteristic);
694 handleOnCharacteristicChanged(gatt, characteristic, characteristic.getValue());
698 public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
699 android.bluetooth.BluetoothGattCharacteristic characteristic,
704 handleOnCharacteristicChanged(gatt, characteristic,
value);
708 public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
709 android.bluetooth.BluetoothGattDescriptor descriptor,
712 super.onDescriptorRead(gatt, descriptor, status);
713 handleOnDescriptorRead(gatt, descriptor, status, descriptor.getValue());
717 public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
718 android.bluetooth.BluetoothGattDescriptor descriptor,
724 handleOnDescriptorRead(gatt, descriptor, status,
value);
727 public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
728 android.bluetooth.BluetoothGattDescriptor descriptor,
731 super.onDescriptorWrite(gatt, descriptor, status);
732 handleOnDescriptorWrite(gatt, descriptor, status);
740 public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt,
int rssi,
int status)
742 super.onReadRemoteRssi(gatt,
rssi, status);
743 handleOnReadRemoteRssi(gatt,
rssi, status);
746 public void onMtuChanged(android.bluetooth.BluetoothGatt gatt,
int mtu,
int status)
748 super.onMtuChanged(gatt,
mtu, status);
749 handleOnMtuChanged(gatt,
mtu, status);
754 public synchronized int mtu() {
755 if (mSupportedMtu == -1) {
758 return mSupportedMtu;
764 if (mBluetoothGatt ==
null)
770 ReadWriteJob newJob =
new ReadWriteJob();
774 if (!readWriteQueue.add(newJob)) {
775 Log.w(TAG,
"Cannot add remote RSSI read to queue" );
779 performNextIOThreaded();
785 BluetoothDevice mRemoteGattDevice;
787 if (mBluetoothAdapter ==
null) {
788 Log.w(TAG,
"Cannot connect, no bluetooth adapter");
793 mRemoteGattDevice = mBluetoothAdapter.getRemoteDevice(mRemoteGattAddress);
794 }
catch (IllegalArgumentException ex) {
795 Log.w(TAG,
"Remote address is not valid: " + mRemoteGattAddress);
802 if (Build.VERSION.SDK_INT >= 27) {
803 HandlerThread handlerThread =
new HandlerThread(
"QtBluetoothLEHandlerThread");
804 handlerThread.start();
805 mHandler =
new Handler(handlerThread.getLooper());
807 Class[]
args =
new Class[6];
808 args[0] = android.content.Context.class;
809 args[1] =
boolean.class;
810 args[2] = android.bluetooth.BluetoothGattCallback.class;
813 args[5] = android.os.Handler.class;
816 Method connectMethod = mRemoteGattDevice.getClass().getDeclaredMethod(
"connectGatt",
args);
817 if (connectMethod !=
null) {
818 mBluetoothGatt = (BluetoothGatt) connectMethod.invoke(mRemoteGattDevice, qtContext,
false,
819 gattCallback, 2 , 1 , mHandler);
820 Log.w(TAG,
"Using Android v26 BluetoothDevice.connectGatt()");
822 }
catch (Exception ex) {
823 Log.w(TAG,
"connectGatt() v26 not available");
824 ex.printStackTrace();
827 if (mBluetoothGatt ==
null) {
828 mHandler.getLooper().quitSafely();
833 if (mBluetoothGatt ==
null) {
837 Class[] constr_args =
new Class[5];
838 constr_args[0] = android.bluetooth.BluetoothGattService.class;
839 constr_args[1] = java.util.UUID.class;
840 constr_args[2] =
int.class;
841 constr_args[3] =
int.class;
842 constr_args[4] =
int.class;
843 mCharacteristicConstructor = BluetoothGattCharacteristic.class.getDeclaredConstructor(constr_args);
844 mCharacteristicConstructor.setAccessible(
true);
845 }
catch (NoSuchMethodException ex) {
846 Log.w(TAG,
"Unable get characteristic constructor. Buffer race condition are possible");
855 mRemoteGattDevice.connectGatt(qtContext,
false,
857 }
catch (IllegalArgumentException ex) {
858 Log.w(TAG,
"Gatt connection failed");
859 ex.printStackTrace();
862 return mBluetoothGatt !=
null;
867 if (mBluetoothGatt ==
null)
870 mBluetoothGatt.disconnect();
876 return mBluetoothGatt !=
null && mBluetoothGatt.discoverServices();
883 private class GattEntry
886 public boolean valueKnown =
false;
887 public BluetoothGattService service =
null;
888 public BluetoothGattCharacteristic characteristic =
null;
889 public BluetoothGattDescriptor descriptor =
null;
896 public int endHandle = -1;
898 public int associatedServiceHandle;
909 private class ReadWriteJob
911 public GattEntry
entry;
912 public byte[] newValue;
913 public int requestedWriteType;
918 private final Hashtable<UUID, List<Integer>> uuidToEntry =
new Hashtable<UUID, List<Integer>>(100);
920 private final ArrayList<GattEntry> entries =
new ArrayList<GattEntry>(100);
922 private final LinkedList<Integer> servicesToBeDiscovered =
new LinkedList<Integer>();
925 private final LinkedList<ReadWriteJob> readWriteQueue =
new LinkedList<ReadWriteJob>();
926 private ReadWriteJob pendingJob;
934 private int handleForCharacteristic(BluetoothGattCharacteristic characteristic)
936 if (characteristic ==
null)
939 List<Integer> handles = uuidToEntry.get(characteristic.getService().getUuid());
940 if (handles ==
null || handles.isEmpty())
944 int serviceHandle = handles.get(0);
948 for (
int i = serviceHandle+1;
i < entries.size();
i++) {
953 switch (
entry.type) {
955 case CharacteristicValue:
960 if (
entry.characteristic == characteristic)
965 }
catch (IndexOutOfBoundsException ex) { }
975 private int handleForDescriptor(BluetoothGattDescriptor descriptor)
977 if (descriptor ==
null)
980 List<Integer> handles = uuidToEntry.get(descriptor.getCharacteristic().getService().getUuid());
981 if (handles ==
null || handles.isEmpty())
985 int serviceHandle = handles.get(0);
989 for (
int i = serviceHandle+1;
i < entries.size();
i++) {
994 switch (
entry.type) {
996 case CharacteristicValue:
1001 if (
entry.descriptor == descriptor)
1006 }
catch (IndexOutOfBoundsException ignored) { }
1011 private void populateHandles()
1016 GattEntry
entry =
null;
1017 List<BluetoothGattService>
services = mBluetoothGatt.getServices();
1018 for (BluetoothGattService service:
services) {
1019 GattEntry serviceEntry =
new GattEntry();
1020 serviceEntry.type = GattEntryType.Service;
1021 serviceEntry.service =
service;
1022 entries.add(serviceEntry);
1025 int serviceHandle = entries.size() - 1;
1027 serviceEntry.associatedServiceHandle = serviceHandle;
1030 List<Integer> old = uuidToEntry.get(
service.getUuid());
1032 old =
new ArrayList<Integer>();
1033 old.add(entries.size()-1);
1034 uuidToEntry.put(
service.getUuid(), old);
1037 List<BluetoothGattCharacteristic> charList =
service.getCharacteristics();
1038 for (BluetoothGattCharacteristic characteristic: charList) {
1039 entry =
new GattEntry();
1040 entry.type = GattEntryType.Characteristic;
1041 entry.characteristic = characteristic;
1042 entry.associatedServiceHandle = serviceHandle;
1047 entry =
new GattEntry();
1048 entry.type = GattEntryType.CharacteristicValue;
1049 entry.associatedServiceHandle = serviceHandle;
1050 entry.endHandle = entries.size();
1054 List<BluetoothGattDescriptor> descList = characteristic.getDescriptors();
1055 for (BluetoothGattDescriptor desc: descList) {
1056 entry =
new GattEntry();
1057 entry.type = GattEntryType.Descriptor;
1058 entry.descriptor =
desc;
1059 entry.associatedServiceHandle = serviceHandle;
1066 serviceEntry.endHandle = entries.size() - 1;
1069 entries.trimToSize();
1072 private void resetData()
1074 uuidToEntry.clear();
1076 servicesToBeDiscovered.clear();
1079 timeoutHandler.removeCallbacksAndMessages(
null);
1080 handleForTimeout.set(HANDLE_FOR_RESET);
1082 readWriteQueue.clear();
1089 Log.d(TAG,
"Discover service details for: " + serviceUuid +
", fullDiscovery: "
1090 + fullDiscovery +
", BluetoothGatt: " + (mBluetoothGatt !=
null));
1092 if (mBluetoothGatt ==
null)
1095 if (entries.isEmpty())
1101 UUID service = UUID.fromString(serviceUuid);
1102 List<Integer> handles = uuidToEntry.get(service);
1103 if (handles ==
null || handles.isEmpty()) {
1104 Log.w(TAG,
"Unknown service uuid for current device: " + service.toString());
1109 serviceHandle = handles.get(0);
1110 entry = entries.get(serviceHandle);
1111 if (
entry ==
null) {
1112 Log.w(TAG,
"Service with UUID " + service.toString() +
" not found");
1115 }
catch (IllegalArgumentException ex) {
1117 Log.w(TAG,
"Cannot parse given UUID");
1122 Log.w(TAG,
"Given UUID is not a service UUID: " + serviceUuid);
1127 if (
entry.valueKnown || servicesToBeDiscovered.contains(serviceHandle)) {
1128 Log.w(TAG,
"Service already known or to be discovered");
1132 servicesToBeDiscovered.add(serviceHandle);
1133 scheduleServiceDetailDiscovery(serviceHandle, fullDiscovery);
1134 performNextIOThreaded();
1135 }
catch (Exception ex) {
1136 ex.printStackTrace();
1149 if (mBluetoothGatt ==
null)
1154 uuid = UUID.fromString(serviceUuid);
1155 }
catch (Exception ex) {
1156 ex.printStackTrace();
1161 BluetoothGattService service = mBluetoothGatt.getService(uuid);
1162 if (service ==
null)
1165 final List<BluetoothGattService> includes = service.getIncludedServices();
1166 if (includes.isEmpty())
1169 StringBuilder builder =
new StringBuilder();
1170 for (BluetoothGattService includedService: includes) {
1171 builder.append(includedService.getUuid().toString()).
append(
" ");
1174 return builder.toString();
1177 private synchronized void finishCurrentServiceDiscovery(
int handleDiscoveredService)
1179 Log.w(TAG,
"Finished current discovery for service handle " + handleDiscoveredService);
1180 GattEntry discoveredService = entries.get(handleDiscoveredService);
1181 discoveredService.valueKnown =
true;
1183 servicesToBeDiscovered.removeFirst();
1184 }
catch (NoSuchElementException ex) {
1185 Log.w(TAG,
"Expected queued service but didn't find any");
1189 handleDiscoveredService + 1, discoveredService.endHandle + 1);
1194 private boolean executeMtuExchange()
1196 if (mBluetoothGatt.requestMtu(MAX_MTU)) {
1197 Log.w(TAG,
"MTU change initiated");
1200 Log.w(TAG,
"MTU change request failed");
1203 Log.w(TAG,
"Assuming default MTU value of 23 bytes");
1204 mSupportedMtu = DEFAULT_MTU;
1208 private boolean executeRemoteRssiRead()
1210 if (mBluetoothGatt.readRemoteRssi()) {
1211 Log.d(TAG,
"RSSI read initiated");
1214 Log.w(TAG,
"Initiating remote RSSI read failed");
1223 private void scheduleMtuExchange() {
1224 ReadWriteJob newJob =
new ReadWriteJob();
1225 newJob.jobType = IoJobType.Mtu;
1226 newJob.entry =
null;
1228 readWriteQueue.add(newJob);
1240 private void scheduleServiceDetailDiscovery(
int serviceHandle,
boolean fullDiscovery)
1242 GattEntry serviceEntry = entries.get(serviceHandle);
1243 final int endHandle = serviceEntry.endHandle;
1245 if (serviceHandle == endHandle) {
1246 Log.w(TAG,
"scheduleServiceDetailDiscovery: service is empty; nothing to discover");
1247 finishCurrentServiceDiscovery(serviceHandle);
1252 for (
int i = serviceHandle + 1;
i <= endHandle;
i++) {
1253 GattEntry
entry = entries.get(
i);
1255 if (
entry.type == GattEntryType.Service) {
1257 Log.w(TAG,
"scheduleServiceDetailDiscovery: wrong endHandle");
1261 ReadWriteJob newJob =
new ReadWriteJob();
1262 newJob.entry =
entry;
1263 if (fullDiscovery) {
1264 newJob.jobType = IoJobType.Read;
1266 newJob.jobType = IoJobType.SkippedRead;
1269 final boolean result = readWriteQueue.add(newJob);
1271 Log.w(TAG,
"Cannot add service discovery job for " + serviceEntry.service.getUuid()
1272 +
" on item " +
entry.type);
1284 if (mBluetoothGatt ==
null)
1289 entry = entries.get(charHandle-1);
1290 }
catch (IndexOutOfBoundsException ex) {
1291 ex.printStackTrace();
1295 ReadWriteJob newJob =
new ReadWriteJob();
1296 newJob.newValue = newValue;
1297 newJob.entry =
entry;
1301 switch (writeMode) {
1303 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
1306 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_SIGNED;
1309 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
1314 result = readWriteQueue.add(newJob);
1317 Log.w(TAG,
"Cannot add characteristic write request for " + charHandle +
" to queue" );
1321 performNextIOThreaded();
1332 if (mBluetoothGatt ==
null)
1337 entry = entries.get(descHandle-1);
1338 }
catch (IndexOutOfBoundsException ex) {
1339 ex.printStackTrace();
1343 ReadWriteJob newJob =
new ReadWriteJob();
1344 newJob.newValue = newValue;
1345 newJob.entry =
entry;
1346 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
1350 result = readWriteQueue.add(newJob);
1353 Log.w(TAG,
"Cannot add descriptor write request for " + descHandle +
" to queue" );
1357 performNextIOThreaded();
1368 if (mBluetoothGatt ==
null)
1373 entry = entries.get(charHandle-1);
1374 }
catch (IndexOutOfBoundsException ex) {
1375 ex.printStackTrace();
1379 ReadWriteJob newJob =
new ReadWriteJob();
1380 newJob.entry =
entry;
1384 result = readWriteQueue.add(newJob);
1387 Log.w(TAG,
"Cannot add characteristic read request for " + charHandle +
" to queue" );
1391 performNextIOThreaded();
1398 if (mBluetoothGatt ==
null)
1403 entry = entries.get(descHandle-1);
1404 }
catch (IndexOutOfBoundsException ex) {
1405 ex.printStackTrace();
1409 ReadWriteJob newJob =
new ReadWriteJob();
1410 newJob.entry =
entry;
1414 result = readWriteQueue.add(newJob);
1417 Log.w(TAG,
"Cannot add descriptor read request for " + descHandle +
" to queue" );
1421 performNextIOThreaded();
1428 private synchronized void interruptCurrentIO(
int handle)
1433 performNextIOThreaded();
1435 if (
handle == HANDLE_FOR_MTU_EXCHANGE ||
handle == HANDLE_FOR_RSSI_READ)
1442 if (
entry.valueKnown)
1444 entry.valueKnown =
true;
1446 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
1447 if (serviceEntry !=
null && serviceEntry.endHandle ==
handle)
1448 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
1449 }
catch (IndexOutOfBoundsException outOfBounds) {
1450 Log.w(TAG,
"interruptCurrentIO(): Unknown gatt entry, index: "
1451 +
handle +
" size: " + entries.size());
1459 private void performNextIOThreaded()
1461 if (mHandler !=
null) {
1462 mHandler.post(
new Runnable() {
1478 private synchronized void performNextIO()
1480 Log.d(TAG,
"Perform next BTLE IO, job queue size: " + readWriteQueue.size()
1481 +
", a job is pending: " + (pendingJob !=
null) +
", BluetoothGatt: "
1482 + (mBluetoothGatt !=
null));
1484 if (mBluetoothGatt ==
null)
1487 boolean skip =
false;
1488 final ReadWriteJob nextJob;
1489 int handle = HANDLE_FOR_RESET;
1491 if (readWriteQueue.isEmpty() || pendingJob !=
null)
1494 nextJob = readWriteQueue.remove();
1496 if (nextJob.jobType == IoJobType.Mtu) {
1497 handle = HANDLE_FOR_MTU_EXCHANGE;
1498 }
else if (nextJob.jobType == IoJobType.Rssi) {
1499 handle = HANDLE_FOR_RSSI_READ;
1501 switch (nextJob.entry.type) {
1502 case Characteristic:
1503 handle = handleForCharacteristic(nextJob.entry.characteristic);
1506 handle = handleForDescriptor(nextJob.entry.descriptor);
1508 case CharacteristicValue:
1509 handle = nextJob.entry.endHandle;
1518 timeoutHandler.removeCallbacksAndMessages(
null);
1519 handleForTimeout.set(modifiedReadWriteHandle(
handle, nextJob.jobType));
1521 switch (nextJob.jobType) {
1523 skip = executeReadJob(nextJob);
1529 skip = executeWriteJob(nextJob);
1532 skip = executeMtuExchange();
1534 skip = executeRemoteRssiRead();
1539 handleForTimeout.set(HANDLE_FOR_RESET);
1541 pendingJob = nextJob;
1542 timeoutHandler.postDelayed(
new TimeoutRunnable(
1543 modifiedReadWriteHandle(
handle, nextJob.jobType)), RUNNABLE_TIMEOUT);
1546 if (nextJob.jobType != IoJobType.Mtu && nextJob.jobType != IoJobType.Rssi) {
1547 Log.d(TAG,
"Performing queued job, handle: " +
handle +
" " + nextJob.jobType +
" (" +
1548 (nextJob.requestedWriteType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) +
1549 ") ValueKnown: " + nextJob.entry.valueKnown +
" Skipping: " + skip +
1550 " " + nextJob.entry.type);
1553 GattEntry
entry = nextJob.entry;
1563 if (
handle > HANDLE_FOR_RESET) {
1567 final boolean isServiceDiscovery = !
entry.valueKnown;
1569 if (isServiceDiscovery) {
1570 entry.valueKnown =
true;
1571 switch (
entry.type) {
1572 case Characteristic:
1574 nextJob.jobType == IoJobType.Read ?
"Non-readable" :
"Skipped reading of"
1575 +
" characteristic " +
entry.characteristic.getUuid()
1576 +
" for service " +
entry.characteristic.getService().getUuid());
1578 handle + 1,
entry.characteristic.getUuid().toString(),
1579 entry.characteristic.getProperties(),
null);
1583 nextJob.jobType == IoJobType.Read ?
"Non-readable" :
"Skipped reading of"
1584 +
" descriptor " +
entry.descriptor.getUuid()
1585 +
" for service/char " +
entry.descriptor.getCharacteristic().getService().getUuid()
1586 +
"/" +
entry.descriptor.getCharacteristic().getUuid());
1588 entry.descriptor.getCharacteristic().getService().getUuid().toString(),
1589 entry.descriptor.getCharacteristic().getUuid().toString(),
1590 handle + 1,
entry.descriptor.getUuid().toString(),
1593 case CharacteristicValue:
1597 Log.w(TAG,
"Scheduling of Service Gatt entry for service discovery should never happen.");
1603 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
1604 if (serviceEntry.endHandle ==
handle)
1605 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
1606 }
catch (IndexOutOfBoundsException outOfBounds) {
1607 Log.w(TAG,
"performNextIO(): Unknown service for entry, index: "
1608 +
entry.associatedServiceHandle +
" size: " + entries.size());
1614 if (nextJob.jobType == IoJobType.Read) {
1615 errorCode = (entry.type == GattEntryType.Characteristic) ?
1618 errorCode = (entry.type == GattEntryType.Characteristic) ?
1630 private BluetoothGattCharacteristic cloneChararacteristic(BluetoothGattCharacteristic
other) {
1632 return (BluetoothGattCharacteristic) mCharacteristicConstructor.newInstance(
other.getService(),
1634 }
catch (Exception ex) {
1635 Log.w(TAG,
"Cloning characteristic failed!" + ex);
1641 private boolean executeWriteJob(ReadWriteJob nextJob)
1644 switch (nextJob.entry.type) {
1645 case Characteristic:
1646 if (Build.VERSION.SDK_INT >= 33) {
1647 int writeResult = mBluetoothGatt.writeCharacteristic(
1648 nextJob.entry.characteristic, nextJob.newValue, nextJob.requestedWriteType);
1649 return (writeResult != BluetoothStatusCodes.SUCCESS);
1651 if (mHandler !=
null || mCharacteristicConstructor ==
null) {
1652 if (nextJob.entry.characteristic.getWriteType() != nextJob.requestedWriteType) {
1653 nextJob.entry.characteristic.setWriteType(nextJob.requestedWriteType);
1655 result = nextJob.entry.characteristic.setValue(nextJob.newValue);
1656 return !
result || !mBluetoothGatt.writeCharacteristic(nextJob.entry.characteristic);
1658 BluetoothGattCharacteristic orig = nextJob.entry.characteristic;
1659 BluetoothGattCharacteristic tmp = cloneChararacteristic(orig);
1662 tmp.setWriteType(nextJob.requestedWriteType);
1663 return !tmp.setValue(nextJob.newValue) || !mBluetoothGatt.writeCharacteristic(tmp);
1666 if (nextJob.entry.descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
1684 boolean enableNotifications =
false;
1685 int value = (nextJob.newValue[0] & 0xff);
1688 enableNotifications =
true;
1691 result = mBluetoothGatt.setCharacteristicNotification(
1692 nextJob.entry.descriptor.getCharacteristic(), enableNotifications);
1694 Log.w(TAG,
"Cannot set characteristic notification");
1699 Log.d(TAG,
"Enable notifications: " + enableNotifications);
1702 if (Build.VERSION.SDK_INT >= 33) {
1703 int writeResult = mBluetoothGatt.writeDescriptor(
1704 nextJob.entry.descriptor, nextJob.newValue);
1705 return (writeResult != BluetoothStatusCodes.SUCCESS);
1707 result = nextJob.entry.descriptor.setValue(nextJob.newValue);
1708 if (!
result || !mBluetoothGatt.writeDescriptor(nextJob.entry.descriptor))
1713 case CharacteristicValue:
1720 private boolean executeReadJob(ReadWriteJob nextJob)
1723 switch (nextJob.entry.type) {
1724 case Characteristic:
1726 result = mBluetoothGatt.readCharacteristic(nextJob.entry.characteristic);
1727 }
catch (java.lang.SecurityException se) {
1729 se.printStackTrace();
1737 result = mBluetoothGatt.readDescriptor(nextJob.entry.descriptor);
1738 }
catch (java.lang.SecurityException se) {
1740 se.printStackTrace();
1748 case CharacteristicValue:
1772 private int modifiedReadWriteHandle(
int handle, IoJobType
type)
1774 int modifiedHandle =
handle;
1777 Log.w(TAG,
"Invalid handle");
1779 modifiedHandle = (modifiedHandle & 0xFFFF);
1783 modifiedHandle = (modifiedHandle | 0x00010000);
1786 modifiedHandle = (modifiedHandle | 0x00020000);
1789 modifiedHandle = HANDLE_FOR_MTU_EXCHANGE;
1792 modifiedHandle = HANDLE_FOR_RSSI_READ;
1796 return modifiedHandle;
1802 if (mBluetoothGatt ==
null)
1805 int requestPriority = 0;
1806 if (minimalInterval < 30)
1807 requestPriority = 1;
1808 else if (minimalInterval > 100)
1809 requestPriority = 2;
1812 return mBluetoothGatt.requestConnectionPriority(requestPriority);
1813 }
catch (IllegalArgumentException ex) {
1814 Log.w(TAG,
"Connection update priority out of range: " + requestPriority);
1824 int startHandle,
int endHandle);
1826 int charHandle, String charUuid,
1829 int descHandle, String descUuid,
byte[]
data);
1835 public native
void leServiceError(
long qtObject,
int attributeHandle,
int errorCode);