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 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)
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
126 || pendingJob.jobType == IoJobType.Mtu || pendingJob.jobType == IoJobType.Rssi) {
131 handleForTimeout.set(HANDLE_FOR_RESET);
134 if (pendingJob ==
null
135 || pendingJob.jobType == IoJobType.Mtu || pendingJob.jobType == IoJobType.Rssi) {
139 readWriteQueue.addFirst(pendingJob);
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,
263 int qLowEnergyController_State = 0;
266 case BluetoothProfile.STATE_DISCONNECTED:
267 if (bondStateBroadcastReceiver !=
null) {
268 qtContext.unregisterReceiver(bondStateBroadcastReceiver);
269 bondStateBroadcastReceiver =
null;
272 qLowEnergyController_State = 0;
277 if (mBluetoothGatt !=
null) {
278 mBluetoothGatt.close();
279 if (mHandler !=
null) {
280 mHandler.getLooper().quitSafely();
284 mBluetoothGatt =
null;
286 case BluetoothProfile.STATE_CONNECTED:
287 if (bondStateBroadcastReceiver ==
null) {
288 bondStateBroadcastReceiver =
new BondStateBroadcastReceiver();
289 qtContext.registerReceiver(bondStateBroadcastReceiver,
292 qLowEnergyController_State = 2;
298 case BluetoothGatt.GATT_SUCCESS:
301 case BluetoothGatt.GATT_FAILURE:
305 Log.w(TAG,
"Connection Error: Try to delay connect() call after previous activity");
311 Log.w(TAG,
"The remote host closed the connection");
319 Log.w(TAG,
"Unhandled error code on connectionStateChanged: "
327 private synchronized void handleOnServicesDiscovered(BluetoothGatt gatt,
int status) {
330 StringBuilder builder =
new StringBuilder();
332 case BluetoothGatt.GATT_SUCCESS:
334 final List<BluetoothGattService>
services = mBluetoothGatt.getServices();
335 for (BluetoothGattService service:
services) {
336 builder.append(
service.getUuid().toString()).append(
" ");
340 Log.w(TAG,
"Unhandled error code on onServicesDiscovered: " + status);
341 errorCode = status;
break;
344 if (status == BluetoothGatt.GATT_SUCCESS)
345 scheduleMtuExchange();
348 private synchronized void handleOnCharacteristicRead(BluetoothGatt gatt,
349 BluetoothGattCharacteristic characteristic,
353 int foundHandle = handleForCharacteristic(characteristic);
354 if (foundHandle == -1 || foundHandle >= entries.size() ) {
355 Log.w(TAG,
"Cannot find characteristic read request for read notification - handle: " +
356 foundHandle +
" size: " + entries.size());
365 boolean requestTimedOut = !handleForTimeout.compareAndSet(
366 modifiedReadWriteHandle(foundHandle, IoJobType.Read),
368 if (requestTimedOut) {
369 Log.w(TAG,
"Late char read reply after timeout was hit for handle " + foundHandle);
375 GattEntry
entry = entries.get(foundHandle);
376 final boolean isServiceDiscoveryRun = !
entry.valueKnown;
377 entry.valueKnown =
true;
379 if (status == BluetoothGatt.GATT_SUCCESS) {
383 characteristic.getService().getUuid().toString(),
384 foundHandle + 1, characteristic.getUuid().toString(),
385 characteristic.getProperties(),
value);
387 if (isServiceDiscoveryRun) {
388 Log.w(TAG,
"onCharacteristicRead during discovery error: " + status);
390 Log.d(TAG,
"Non-readable characteristic " + characteristic.getUuid() +
391 " for service " + characteristic.getService().getUuid());
393 foundHandle + 1, characteristic.getUuid().toString(),
394 characteristic.getProperties(),
value);
397 final int characteristicReadError = 5;
398 leServiceError(qtObject, foundHandle + 1, characteristicReadError);
402 if (isServiceDiscoveryRun) {
405 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
406 if (serviceEntry.endHandle == foundHandle)
407 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
416 private synchronized void handleOnCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
417 android.bluetooth.BluetoothGattCharacteristic characteristic,
420 int handle = handleForCharacteristic(characteristic);
422 Log.w(TAG,
"onCharacteristicChanged: cannot find handle");
429 private synchronized void handleOnCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
430 android.bluetooth.BluetoothGattCharacteristic characteristic,
433 if (status != BluetoothGatt.GATT_SUCCESS)
434 Log.w(TAG,
"onCharacteristicWrite: error " + status);
436 int handle = handleForCharacteristic(characteristic);
438 Log.w(TAG,
"onCharacteristicWrite: cannot find handle");
442 boolean requestTimedOut = !handleForTimeout.compareAndSet(
443 modifiedReadWriteHandle(
handle, IoJobType.Write),
445 if (requestTimedOut) {
446 Log.w(TAG,
"Late char write reply after timeout was hit for handle " +
handle);
455 case BluetoothGatt.GATT_SUCCESS:
464 value = pendingJob.newValue;
471 private synchronized void handleOnDescriptorRead(android.bluetooth.BluetoothGatt gatt,
472 android.bluetooth.BluetoothGattDescriptor descriptor,
473 int status,
byte[] newValue)
475 int foundHandle = handleForDescriptor(descriptor);
476 if (foundHandle == -1 || foundHandle >= entries.size() ) {
477 Log.w(TAG,
"Cannot find descriptor read request for read notification - handle: " +
478 foundHandle +
" size: " + entries.size());
487 boolean requestTimedOut = !handleForTimeout.compareAndSet(
488 modifiedReadWriteHandle(foundHandle, IoJobType.Read),
490 if (requestTimedOut) {
491 Log.w(TAG,
"Late descriptor read reply after timeout was hit for handle " +
498 GattEntry
entry = entries.get(foundHandle);
499 final boolean isServiceDiscoveryRun = !
entry.valueKnown;
500 entry.valueKnown =
true;
502 if (status == BluetoothGatt.GATT_SUCCESS) {
505 descriptor.getCharacteristic().getService().getUuid().toString(),
506 descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
507 descriptor.getUuid().toString(), newValue);
509 if (isServiceDiscoveryRun) {
512 Log.w(TAG,
"onDescriptorRead during discovery error: " + status);
513 Log.d(TAG,
"Non-readable descriptor " + descriptor.getUuid() +
514 " for characteristic " + descriptor.getCharacteristic().getUuid() +
515 " for service " + descriptor.getCharacteristic().getService().getUuid());
517 descriptor.getCharacteristic().getService().getUuid().toString(),
518 descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1,
519 descriptor.getUuid().toString(), newValue);
522 final int descriptorReadError = 6;
528 if (isServiceDiscoveryRun) {
530 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
531 if (serviceEntry.endHandle == foundHandle) {
532 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
541 if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
545 if ((
value & 0x03) > 0) {
546 Log.d(TAG,
"Found descriptor with automatic notifications.");
547 mBluetoothGatt.setCharacteristicNotification(
548 descriptor.getCharacteristic(),
true);
559 private synchronized void handleOnDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
560 android.bluetooth.BluetoothGattDescriptor descriptor,
563 if (status != BluetoothGatt.GATT_SUCCESS)
564 Log.w(TAG,
"onDescriptorWrite: error " + status);
566 int handle = handleForDescriptor(descriptor);
568 boolean requestTimedOut = !handleForTimeout.compareAndSet(
569 modifiedReadWriteHandle(
handle, IoJobType.Write),
571 if (requestTimedOut) {
572 Log.w(TAG,
"Late descriptor write reply after timeout was hit for handle " +
582 case BluetoothGatt.GATT_SUCCESS:
583 errorCode = 0;
break;
585 errorCode = 3;
break;
588 byte[]
value = pendingJob.newValue;
595 private synchronized void handleOnMtuChanged(android.bluetooth.BluetoothGatt gatt,
598 int previousMtu = mSupportedMtu;
599 if (status == BluetoothGatt.GATT_SUCCESS) {
600 Log.w(TAG,
"MTU changed to " +
mtu);
603 Log.w(TAG,
"MTU change error " + status +
". New MTU " +
mtu);
604 mSupportedMtu = DEFAULT_MTU;
606 if (previousMtu != mSupportedMtu)
609 boolean requestTimedOut = !handleForTimeout.compareAndSet(
610 modifiedReadWriteHandle(HANDLE_FOR_MTU_EXCHANGE, IoJobType.Mtu), HANDLE_FOR_RESET);
611 if (requestTimedOut) {
612 Log.w(TAG,
"Late mtu reply after timeout was hit");
623 private synchronized void handleOnReadRemoteRssi(android.bluetooth.BluetoothGatt gatt,
624 int rssi,
int status)
626 Log.d(TAG,
"RSSI read callback, rssi: " +
rssi +
", status: " + status);
629 boolean requestTimedOut = !handleForTimeout.compareAndSet(
630 modifiedReadWriteHandle(HANDLE_FOR_RSSI_READ, IoJobType.Rssi), HANDLE_FOR_RESET);
631 if (requestTimedOut) {
632 Log.w(TAG,
"Late RSSI read reply after timeout was hit");
645 private final BluetoothGattCallback gattCallback =
new BluetoothGattCallback() {
647 public void onConnectionStateChange(BluetoothGatt gatt,
int status,
int newState) {
648 super.onConnectionStateChange(gatt, status,
newState);
649 handleOnConnectionStateChange(gatt, status,
newState);
652 public void onServicesDiscovered(BluetoothGatt gatt,
int status) {
653 super.onServicesDiscovered(gatt, status);
654 handleOnServicesDiscovered(gatt, status);
659 public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
660 android.bluetooth.BluetoothGattCharacteristic characteristic,
663 super.onCharacteristicRead(gatt, characteristic, status);
664 handleOnCharacteristicRead(gatt, characteristic, characteristic.getValue(), status);
668 public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
669 android.bluetooth.BluetoothGattCharacteristic characteristic,
675 handleOnCharacteristicRead(gatt, characteristic,
value, status);
678 public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
679 android.bluetooth.BluetoothGattCharacteristic characteristic,
682 super.onCharacteristicWrite(gatt, characteristic, status);
683 handleOnCharacteristicWrite(gatt, characteristic, status);
687 public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
688 android.bluetooth.BluetoothGattCharacteristic characteristic)
690 super.onCharacteristicChanged(gatt, characteristic);
691 handleOnCharacteristicChanged(gatt, characteristic, characteristic.getValue());
695 public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
696 android.bluetooth.BluetoothGattCharacteristic characteristic,
701 handleOnCharacteristicChanged(gatt, characteristic,
value);
705 public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
706 android.bluetooth.BluetoothGattDescriptor descriptor,
709 super.onDescriptorRead(gatt, descriptor, status);
710 handleOnDescriptorRead(gatt, descriptor, status, descriptor.getValue());
714 public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
715 android.bluetooth.BluetoothGattDescriptor descriptor,
721 handleOnDescriptorRead(gatt, descriptor, status,
value);
724 public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
725 android.bluetooth.BluetoothGattDescriptor descriptor,
728 super.onDescriptorWrite(gatt, descriptor, status);
729 handleOnDescriptorWrite(gatt, descriptor, status);
737 public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt,
int rssi,
int status)
739 super.onReadRemoteRssi(gatt,
rssi, status);
740 handleOnReadRemoteRssi(gatt,
rssi, status);
743 public void onMtuChanged(android.bluetooth.BluetoothGatt gatt,
int mtu,
int status)
745 super.onMtuChanged(gatt,
mtu, status);
746 handleOnMtuChanged(gatt,
mtu, status);
751 public synchronized int mtu() {
752 if (mSupportedMtu == -1) {
755 return mSupportedMtu;
761 if (mBluetoothGatt ==
null)
767 ReadWriteJob newJob =
new ReadWriteJob();
768 newJob.jobType = IoJobType.Rssi;
771 if (!readWriteQueue.add(newJob)) {
772 Log.w(TAG,
"Cannot add remote RSSI read to queue" );
776 performNextIOThreaded();
784 if (mBluetoothAdapter ==
null) {
785 Log.w(TAG,
"Cannot connect, no bluetooth adapter");
790 mRemoteGattDevice = mBluetoothAdapter.getRemoteDevice(mRemoteGattAddress);
791 }
catch (IllegalArgumentException ex) {
792 Log.w(TAG,
"Remote address is not valid: " + mRemoteGattAddress);
799 if (Build.VERSION.SDK_INT >= 27) {
800 HandlerThread handlerThread =
new HandlerThread(
"QtBluetoothLEHandlerThread");
801 handlerThread.start();
802 mHandler =
new Handler(handlerThread.getLooper());
804 Class[]
args =
new Class[6];
805 args[0] = android.content.Context.class;
806 args[1] =
boolean.class;
807 args[2] = android.bluetooth.BluetoothGattCallback.class;
810 args[5] = android.os.Handler.class;
813 Method connectMethod = mRemoteGattDevice.getClass().getDeclaredMethod(
"connectGatt",
args);
814 if (connectMethod !=
null) {
815 mBluetoothGatt = (BluetoothGatt) connectMethod.invoke(mRemoteGattDevice, qtContext,
false,
816 gattCallback, 2 , 1 , mHandler);
817 Log.w(TAG,
"Using Android v26 BluetoothDevice.connectGatt()");
819 }
catch (Exception ex) {
820 Log.w(TAG,
"connectGatt() v26 not available");
821 ex.printStackTrace();
824 if (mBluetoothGatt ==
null) {
825 mHandler.getLooper().quitSafely();
830 if (mBluetoothGatt ==
null) {
834 Class[] constr_args =
new Class[5];
835 constr_args[0] = android.bluetooth.BluetoothGattService.class;
836 constr_args[1] = java.util.UUID.class;
837 constr_args[2] =
int.class;
838 constr_args[3] =
int.class;
839 constr_args[4] =
int.class;
840 mCharacteristicConstructor = BluetoothGattCharacteristic.class.getDeclaredConstructor(constr_args);
841 mCharacteristicConstructor.setAccessible(
true);
842 }
catch (NoSuchMethodException ex) {
843 Log.w(TAG,
"Unable get characteristic constructor. Buffer race condition are possible");
852 mRemoteGattDevice.connectGatt(qtContext,
false,
854 }
catch (IllegalArgumentException ex) {
855 Log.w(TAG,
"Gatt connection failed");
856 ex.printStackTrace();
859 return mBluetoothGatt !=
null;
864 if (mBluetoothGatt ==
null)
867 mBluetoothGatt.disconnect();
873 return mBluetoothGatt !=
null && mBluetoothGatt.discoverServices();
876 private enum GattEntryType
878 Service, Characteristic, CharacteristicValue, Descriptor
880 private class GattEntry
882 public GattEntryType
type;
883 public boolean valueKnown =
false;
884 public BluetoothGattService service =
null;
885 public BluetoothGattCharacteristic characteristic =
null;
886 public BluetoothGattDescriptor descriptor =
null;
893 public int endHandle = -1;
895 public int associatedServiceHandle;
898 private enum IoJobType
906 private class ReadWriteJob
908 public GattEntry
entry;
909 public byte[] newValue;
910 public int requestedWriteType;
911 public IoJobType jobType;
915 private final Hashtable<UUID, List<Integer>> uuidToEntry =
new Hashtable<UUID, List<Integer>>(100);
917 private final ArrayList<GattEntry> entries =
new ArrayList<GattEntry>(100);
919 private final LinkedList<Integer> servicesToBeDiscovered =
new LinkedList<Integer>();
922 private final LinkedList<ReadWriteJob> readWriteQueue =
new LinkedList<ReadWriteJob>();
923 private ReadWriteJob pendingJob;
931 private int handleForCharacteristic(BluetoothGattCharacteristic characteristic)
933 if (characteristic ==
null)
936 List<Integer> handles = uuidToEntry.get(characteristic.getService().getUuid());
937 if (handles ==
null || handles.isEmpty())
941 int serviceHandle = handles.get(0);
945 for (
int i = serviceHandle+1;
i < entries.size();
i++) {
950 switch (
entry.type) {
952 case CharacteristicValue:
957 if (
entry.characteristic == characteristic)
962 }
catch (IndexOutOfBoundsException ex) { }
972 private int handleForDescriptor(BluetoothGattDescriptor descriptor)
974 if (descriptor ==
null)
977 List<Integer> handles = uuidToEntry.get(descriptor.getCharacteristic().getService().getUuid());
978 if (handles ==
null || handles.isEmpty())
982 int serviceHandle = handles.get(0);
986 for (
int i = serviceHandle+1;
i < entries.size();
i++) {
991 switch (
entry.type) {
993 case CharacteristicValue:
998 if (
entry.descriptor == descriptor)
1003 }
catch (IndexOutOfBoundsException ignored) { }
1008 private void populateHandles()
1013 GattEntry
entry =
null;
1014 List<BluetoothGattService>
services = mBluetoothGatt.getServices();
1015 for (BluetoothGattService service:
services) {
1016 GattEntry serviceEntry =
new GattEntry();
1017 serviceEntry.type = GattEntryType.Service;
1018 serviceEntry.service =
service;
1019 entries.add(serviceEntry);
1022 int serviceHandle = entries.size() - 1;
1024 serviceEntry.associatedServiceHandle = serviceHandle;
1027 List<Integer> old = uuidToEntry.get(
service.getUuid());
1029 old =
new ArrayList<Integer>();
1030 old.add(entries.size()-1);
1031 uuidToEntry.put(
service.getUuid(), old);
1034 List<BluetoothGattCharacteristic> charList =
service.getCharacteristics();
1035 for (BluetoothGattCharacteristic characteristic: charList) {
1036 entry =
new GattEntry();
1037 entry.type = GattEntryType.Characteristic;
1038 entry.characteristic = characteristic;
1039 entry.associatedServiceHandle = serviceHandle;
1044 entry =
new GattEntry();
1045 entry.type = GattEntryType.CharacteristicValue;
1046 entry.associatedServiceHandle = serviceHandle;
1047 entry.endHandle = entries.size();
1051 List<BluetoothGattDescriptor> descList = characteristic.getDescriptors();
1052 for (BluetoothGattDescriptor
desc: descList) {
1053 entry =
new GattEntry();
1054 entry.type = GattEntryType.Descriptor;
1055 entry.descriptor =
desc;
1056 entry.associatedServiceHandle = serviceHandle;
1063 serviceEntry.endHandle = entries.size() - 1;
1066 entries.trimToSize();
1069 private void resetData()
1071 uuidToEntry.clear();
1073 servicesToBeDiscovered.clear();
1076 timeoutHandler.removeCallbacksAndMessages(
null);
1077 handleForTimeout.set(HANDLE_FOR_RESET);
1079 readWriteQueue.clear();
1086 if (mBluetoothGatt ==
null)
1089 if (entries.isEmpty())
1095 UUID service = UUID.fromString(serviceUuid);
1096 List<Integer> handles = uuidToEntry.get(service);
1097 if (handles ==
null || handles.isEmpty()) {
1098 Log.w(TAG,
"Unknown service uuid for current device: " + service.toString());
1103 serviceHandle = handles.get(0);
1104 entry = entries.get(serviceHandle);
1105 if (
entry ==
null) {
1106 Log.w(TAG,
"Service with UUID " + service.toString() +
" not found");
1109 }
catch (IllegalArgumentException ex) {
1111 Log.w(TAG,
"Cannot parse given UUID");
1115 if (
entry.type != GattEntryType.Service) {
1116 Log.w(TAG,
"Given UUID is not a service UUID: " + serviceUuid);
1121 if (
entry.valueKnown || servicesToBeDiscovered.contains(serviceHandle)) {
1122 Log.w(TAG,
"Service already known or to be discovered");
1126 servicesToBeDiscovered.add(serviceHandle);
1127 scheduleServiceDetailDiscovery(serviceHandle, fullDiscovery);
1128 performNextIOThreaded();
1129 }
catch (Exception ex) {
1130 ex.printStackTrace();
1143 if (mBluetoothGatt ==
null)
1148 uuid = UUID.fromString(serviceUuid);
1149 }
catch (Exception ex) {
1150 ex.printStackTrace();
1155 BluetoothGattService service = mBluetoothGatt.getService(uuid);
1156 if (service ==
null)
1159 final List<BluetoothGattService> includes = service.getIncludedServices();
1160 if (includes.isEmpty())
1163 StringBuilder builder =
new StringBuilder();
1164 for (BluetoothGattService includedService: includes) {
1165 builder.append(includedService.getUuid().toString()).
append(
" ");
1168 return builder.toString();
1171 private synchronized void finishCurrentServiceDiscovery(
int handleDiscoveredService)
1173 Log.w(TAG,
"Finished current discovery for service handle " + handleDiscoveredService);
1174 GattEntry discoveredService = entries.get(handleDiscoveredService);
1175 discoveredService.valueKnown =
true;
1177 servicesToBeDiscovered.removeFirst();
1178 }
catch (NoSuchElementException ex) {
1179 Log.w(TAG,
"Expected queued service but didn't find any");
1183 handleDiscoveredService + 1, discoveredService.endHandle + 1);
1188 private boolean executeMtuExchange()
1190 if (mBluetoothGatt.requestMtu(MAX_MTU)) {
1191 Log.w(TAG,
"MTU change initiated");
1194 Log.w(TAG,
"MTU change request failed");
1197 Log.w(TAG,
"Assuming default MTU value of 23 bytes");
1198 mSupportedMtu = DEFAULT_MTU;
1202 private boolean executeRemoteRssiRead()
1204 if (mBluetoothGatt.readRemoteRssi()) {
1205 Log.d(TAG,
"RSSI read initiated");
1208 Log.w(TAG,
"Initiating remote RSSI read failed");
1217 private void scheduleMtuExchange() {
1218 ReadWriteJob newJob =
new ReadWriteJob();
1219 newJob.jobType = IoJobType.Mtu;
1220 newJob.entry =
null;
1222 readWriteQueue.add(newJob);
1234 private void scheduleServiceDetailDiscovery(
int serviceHandle,
boolean fullDiscovery)
1236 GattEntry serviceEntry = entries.get(serviceHandle);
1237 final int endHandle = serviceEntry.endHandle;
1239 if (serviceHandle == endHandle) {
1240 Log.w(TAG,
"scheduleServiceDetailDiscovery: service is empty; nothing to discover");
1241 finishCurrentServiceDiscovery(serviceHandle);
1246 for (
int i = serviceHandle + 1;
i <= endHandle;
i++) {
1247 GattEntry
entry = entries.get(
i);
1249 if (
entry.type == GattEntryType.Service) {
1251 Log.w(TAG,
"scheduleServiceDetailDiscovery: wrong endHandle");
1255 ReadWriteJob newJob =
new ReadWriteJob();
1256 newJob.entry =
entry;
1257 if (fullDiscovery) {
1258 newJob.jobType = IoJobType.Read;
1260 newJob.jobType = IoJobType.SkippedRead;
1263 final boolean result = readWriteQueue.add(newJob);
1265 Log.w(TAG,
"Cannot add service discovery job for " + serviceEntry.service.getUuid()
1266 +
" on item " +
entry.type);
1278 if (mBluetoothGatt ==
null)
1283 entry = entries.get(charHandle-1);
1284 }
catch (IndexOutOfBoundsException ex) {
1285 ex.printStackTrace();
1289 ReadWriteJob newJob =
new ReadWriteJob();
1290 newJob.newValue = newValue;
1291 newJob.entry =
entry;
1292 newJob.jobType = IoJobType.Write;
1295 switch (writeMode) {
1297 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
1300 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_SIGNED;
1303 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
1308 result = readWriteQueue.add(newJob);
1311 Log.w(TAG,
"Cannot add characteristic write request for " + charHandle +
" to queue" );
1315 performNextIOThreaded();
1326 if (mBluetoothGatt ==
null)
1331 entry = entries.get(descHandle-1);
1332 }
catch (IndexOutOfBoundsException ex) {
1333 ex.printStackTrace();
1337 ReadWriteJob newJob =
new ReadWriteJob();
1338 newJob.newValue = newValue;
1339 newJob.entry =
entry;
1340 newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
1341 newJob.jobType = IoJobType.Write;
1344 result = readWriteQueue.add(newJob);
1347 Log.w(TAG,
"Cannot add descriptor write request for " + descHandle +
" to queue" );
1351 performNextIOThreaded();
1362 if (mBluetoothGatt ==
null)
1367 entry = entries.get(charHandle-1);
1368 }
catch (IndexOutOfBoundsException ex) {
1369 ex.printStackTrace();
1373 ReadWriteJob newJob =
new ReadWriteJob();
1374 newJob.entry =
entry;
1375 newJob.jobType = IoJobType.Read;
1378 result = readWriteQueue.add(newJob);
1381 Log.w(TAG,
"Cannot add characteristic read request for " + charHandle +
" to queue" );
1385 performNextIOThreaded();
1392 if (mBluetoothGatt ==
null)
1397 entry = entries.get(descHandle-1);
1398 }
catch (IndexOutOfBoundsException ex) {
1399 ex.printStackTrace();
1403 ReadWriteJob newJob =
new ReadWriteJob();
1404 newJob.entry =
entry;
1405 newJob.jobType = IoJobType.Read;
1408 result = readWriteQueue.add(newJob);
1411 Log.w(TAG,
"Cannot add descriptor read request for " + descHandle +
" to queue" );
1415 performNextIOThreaded();
1422 private synchronized void interruptCurrentIO(
int handle)
1427 performNextIOThreaded();
1429 if (
handle == HANDLE_FOR_MTU_EXCHANGE ||
handle == HANDLE_FOR_RSSI_READ)
1436 if (
entry.valueKnown)
1438 entry.valueKnown =
true;
1440 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
1441 if (serviceEntry !=
null && serviceEntry.endHandle ==
handle)
1442 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
1443 }
catch (IndexOutOfBoundsException outOfBounds) {
1444 Log.w(TAG,
"interruptCurrentIO(): Unknown gatt entry, index: "
1445 +
handle +
" size: " + entries.size());
1453 private void performNextIOThreaded()
1455 if (mHandler !=
null) {
1456 mHandler.post(
new Runnable() {
1472 private synchronized void performNextIO()
1474 if (mBluetoothGatt ==
null)
1477 boolean skip =
false;
1478 final ReadWriteJob nextJob;
1479 int handle = HANDLE_FOR_RESET;
1481 if (readWriteQueue.isEmpty() || pendingJob !=
null)
1484 nextJob = readWriteQueue.remove();
1486 if (nextJob.jobType == IoJobType.Mtu) {
1487 handle = HANDLE_FOR_MTU_EXCHANGE;
1488 }
else if (nextJob.jobType == IoJobType.Rssi) {
1489 handle = HANDLE_FOR_RSSI_READ;
1491 switch (nextJob.entry.type) {
1492 case Characteristic:
1493 handle = handleForCharacteristic(nextJob.entry.characteristic);
1496 handle = handleForDescriptor(nextJob.entry.descriptor);
1498 case CharacteristicValue:
1499 handle = nextJob.entry.endHandle;
1508 timeoutHandler.removeCallbacksAndMessages(
null);
1509 handleForTimeout.set(modifiedReadWriteHandle(
handle, nextJob.jobType));
1511 switch (nextJob.jobType) {
1513 skip = executeReadJob(nextJob);
1519 skip = executeWriteJob(nextJob);
1522 skip = executeMtuExchange();
1524 skip = executeRemoteRssiRead();
1529 handleForTimeout.set(HANDLE_FOR_RESET);
1531 pendingJob = nextJob;
1532 timeoutHandler.postDelayed(
new TimeoutRunnable(
1533 modifiedReadWriteHandle(
handle, nextJob.jobType)), RUNNABLE_TIMEOUT);
1536 if (nextJob.jobType != IoJobType.Mtu && nextJob.jobType != IoJobType.Rssi) {
1537 Log.w(TAG,
"Performing queued job, handle: " +
handle +
" " + nextJob.jobType +
" (" +
1538 (nextJob.requestedWriteType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) +
1539 ") ValueKnown: " + nextJob.entry.valueKnown +
" Skipping: " + skip +
1540 " " + nextJob.entry.type);
1543 GattEntry
entry = nextJob.entry;
1553 if (
handle > HANDLE_FOR_RESET) {
1557 final boolean isServiceDiscovery = !
entry.valueKnown;
1559 if (isServiceDiscovery) {
1560 entry.valueKnown =
true;
1561 switch (
entry.type) {
1562 case Characteristic:
1564 nextJob.jobType == IoJobType.Read ?
"Non-readable" :
"Skipped reading of"
1565 +
" characteristic " +
entry.characteristic.getUuid()
1566 +
" for service " +
entry.characteristic.getService().getUuid());
1568 handle + 1,
entry.characteristic.getUuid().toString(),
1569 entry.characteristic.getProperties(),
null);
1573 nextJob.jobType == IoJobType.Read ?
"Non-readable" :
"Skipped reading of"
1574 +
" descriptor " +
entry.descriptor.getUuid()
1575 +
" for service/char " +
entry.descriptor.getCharacteristic().getService().getUuid()
1576 +
"/" +
entry.descriptor.getCharacteristic().getUuid());
1578 entry.descriptor.getCharacteristic().getService().getUuid().toString(),
1579 entry.descriptor.getCharacteristic().getUuid().toString(),
1580 handle + 1,
entry.descriptor.getUuid().toString(),
1583 case CharacteristicValue:
1587 Log.w(TAG,
"Scheduling of Service Gatt entry for service discovery should never happen.");
1593 GattEntry serviceEntry = entries.get(
entry.associatedServiceHandle);
1594 if (serviceEntry.endHandle ==
handle)
1595 finishCurrentServiceDiscovery(
entry.associatedServiceHandle);
1596 }
catch (IndexOutOfBoundsException outOfBounds) {
1597 Log.w(TAG,
"performNextIO(): Unknown service for entry, index: "
1598 +
entry.associatedServiceHandle +
" size: " + entries.size());
1604 if (nextJob.jobType == IoJobType.Read) {
1605 errorCode = (entry.type == GattEntryType.Characteristic) ?
1608 errorCode = (entry.type == GattEntryType.Characteristic) ?
1620 private BluetoothGattCharacteristic cloneChararacteristic(BluetoothGattCharacteristic
other) {
1622 return (BluetoothGattCharacteristic) mCharacteristicConstructor.newInstance(
other.getService(),
1624 }
catch (Exception ex) {
1625 Log.w(TAG,
"Cloning characteristic failed!" + ex);
1631 private boolean executeWriteJob(ReadWriteJob nextJob)
1634 switch (nextJob.entry.type) {
1635 case Characteristic:
1636 if (Build.VERSION.SDK_INT >= 33) {
1637 int writeResult = mBluetoothGatt.writeCharacteristic(
1638 nextJob.entry.characteristic, nextJob.newValue, nextJob.requestedWriteType);
1639 return (writeResult != BluetoothStatusCodes.SUCCESS);
1641 if (mHandler !=
null || mCharacteristicConstructor ==
null) {
1642 if (nextJob.entry.characteristic.getWriteType() != nextJob.requestedWriteType) {
1643 nextJob.entry.characteristic.setWriteType(nextJob.requestedWriteType);
1645 result = nextJob.entry.characteristic.setValue(nextJob.newValue);
1646 return !
result || !mBluetoothGatt.writeCharacteristic(nextJob.entry.characteristic);
1648 BluetoothGattCharacteristic orig = nextJob.entry.characteristic;
1649 BluetoothGattCharacteristic tmp = cloneChararacteristic(orig);
1652 tmp.setWriteType(nextJob.requestedWriteType);
1653 return !tmp.setValue(nextJob.newValue) || !mBluetoothGatt.writeCharacteristic(tmp);
1656 if (nextJob.entry.descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) {
1674 boolean enableNotifications =
false;
1675 int value = (nextJob.newValue[0] & 0xff);
1678 enableNotifications =
true;
1681 result = mBluetoothGatt.setCharacteristicNotification(
1682 nextJob.entry.descriptor.getCharacteristic(), enableNotifications);
1684 Log.w(TAG,
"Cannot set characteristic notification");
1689 Log.d(TAG,
"Enable notifications: " + enableNotifications);
1692 if (Build.VERSION.SDK_INT >= 33) {
1693 int writeResult = mBluetoothGatt.writeDescriptor(
1694 nextJob.entry.descriptor, nextJob.newValue);
1695 return (writeResult != BluetoothStatusCodes.SUCCESS);
1697 result = nextJob.entry.descriptor.setValue(nextJob.newValue);
1698 if (!
result || !mBluetoothGatt.writeDescriptor(nextJob.entry.descriptor))
1703 case CharacteristicValue:
1710 private boolean executeReadJob(ReadWriteJob nextJob)
1713 switch (nextJob.entry.type) {
1714 case Characteristic:
1716 result = mBluetoothGatt.readCharacteristic(nextJob.entry.characteristic);
1717 }
catch (java.lang.SecurityException se) {
1719 se.printStackTrace();
1727 result = mBluetoothGatt.readDescriptor(nextJob.entry.descriptor);
1728 }
catch (java.lang.SecurityException se) {
1730 se.printStackTrace();
1738 case CharacteristicValue:
1762 private int modifiedReadWriteHandle(
int handle, IoJobType
type)
1764 int modifiedHandle =
handle;
1767 Log.w(TAG,
"Invalid handle");
1769 modifiedHandle = (modifiedHandle & 0xFFFF);
1773 modifiedHandle = (modifiedHandle | 0x00010000);
1776 modifiedHandle = (modifiedHandle | 0x00020000);
1779 modifiedHandle = HANDLE_FOR_MTU_EXCHANGE;
1782 modifiedHandle = HANDLE_FOR_RSSI_READ;
1786 return modifiedHandle;
1792 if (mBluetoothGatt ==
null)
1795 int requestPriority = 0;
1796 if (minimalInterval < 30)
1797 requestPriority = 1;
1798 else if (minimalInterval > 100)
1799 requestPriority = 2;
1802 return mBluetoothGatt.requestConnectionPriority(requestPriority);
1803 }
catch (IllegalArgumentException ex) {
1804 Log.w(TAG,
"Connection update priority out of range: " + requestPriority);
1814 int startHandle,
int endHandle);
1816 int charHandle, String charUuid,
1819 int descHandle, String descUuid,
byte[]
data);
1825 public native
void leServiceError(
long qtObject,
int attributeHandle,
int errorCode);
id< QT_MANGLE_NAMESPACE(GCDTimerDelegate)> timeoutHandler
IOBluetoothDevice * device
std::vector< ObjCStrongReference< CBMutableService > > services
boolean scanForLeDevice(final boolean isEnabled)
native void leScanResult(long qtObject, BluetoothDevice device, int rssi, byte[] scanRecord)
QtBluetoothLE(final String remoteAddress, Context context)
native void leRemoteRssiRead(long qtObject, int rssi, boolean success)
native void leConnectionStateChange(long qtObject, int wasErrorTransition, int newState)
native void leServicesDiscovered(long qtObject, int errorCode, String uuidList)
native void leMtuChanged(long qtObject, int mtu)
synchronized boolean readRemoteRssi()
synchronized boolean readCharacteristic(int charHandle)
native void leDescriptorWritten(long qtObject, int charHandle, byte[] newData, int errorCode)
synchronized boolean readDescriptor(int descHandle)
native void leServiceError(long qtObject, int attributeHandle, int errorCode)
native void leCharacteristicWritten(long qtObject, int charHandle, byte[] newData, int errorCode)
synchronized void disconnect()
synchronized boolean connect()
synchronized boolean writeCharacteristic(int charHandle, byte[] newValue, int writeMode)
native void leDescriptorRead(long qtObject, String serviceUuid, String charUuid, int descHandle, String descUuid, byte[] data)
synchronized boolean discoverServiceDetails(String serviceUuid, boolean fullDiscovery)
native void leCharacteristicChanged(long qtObject, int charHandle, byte[] newData)
native void leCharacteristicRead(long qtObject, String serviceUuid, int charHandle, String charUuid, int properties, byte[] data)
synchronized boolean discoverServices()
synchronized boolean writeDescriptor(int descHandle, byte[] newValue)
native void leServiceDetailDiscoveryFinished(long qtObject, final String serviceUuid, int startHandle, int endHandle)
synchronized boolean requestConnectionUpdatePriority(double minimalInterval)
synchronized String includedServices(String serviceUuid)
list append(new Employee("Blackpool", "Stephen"))
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
Q_CORE_EXPORT QtJniTypes::Service service()
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