diff options
Diffstat (limited to 'src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java')
-rw-r--r-- | src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java | 1532 |
1 files changed, 0 insertions, 1532 deletions
diff --git a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java b/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java deleted file mode 100644 index 8a69b4c7..00000000 --- a/src/android/bluetooth/src/org/qtproject/qt5/android/bluetooth/QtBluetoothLE.java +++ /dev/null @@ -1,1532 +0,0 @@ -/**************************************************************************** - ** - ** Copyright (C) 2019 The Qt Company Ltd. - ** Contact: https://www.qt.io/licensing/ - ** - ** This file is part of the QtBluetooth module of the Qt Toolkit. - ** - ** $QT_BEGIN_LICENSE:LGPL$ - ** Commercial License Usage - ** Licensees holding valid commercial Qt licenses may use this file in - ** accordance with the commercial license agreement provided with the - ** Software or, alternatively, in accordance with the terms contained in - ** a written agreement between you and The Qt Company. For licensing terms - ** and conditions see https://www.qt.io/terms-conditions. For further - ** information use the contact form at https://www.qt.io/contact-us. - ** - ** GNU Lesser General Public License Usage - ** Alternatively, this file may be used under the terms of the GNU Lesser - ** General Public License version 3 as published by the Free Software - ** Foundation and appearing in the file LICENSE.LGPL3 included in the - ** packaging of this file. Please review the following information to - ** ensure the GNU Lesser General Public License version 3 requirements - ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. - ** - ** GNU General Public License Usage - ** Alternatively, this file may be used under the terms of the GNU - ** General Public License version 2.0 or (at your option) the GNU General - ** Public license version 3 or any later version approved by the KDE Free - ** Qt Foundation. The licenses are as published by the Free Software - ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 - ** included in the packaging of this file. Please review the following - ** information to ensure the GNU General Public License requirements will - ** be met: https://www.gnu.org/licenses/gpl-2.0.html and - ** https://www.gnu.org/licenses/gpl-3.0.html. - ** - ** $QT_END_LICENSE$ - ** - ****************************************************************************/ - -package org.qtproject.qt5.android.bluetooth; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.le.BluetoothLeScanner; -import android.bluetooth.le.ScanCallback; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanResult; -import android.bluetooth.le.ScanSettings; -import android.content.Context; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import java.lang.reflect.Method; -import java.util.concurrent.atomic.AtomicInteger; -import java.lang.reflect.Method; - -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.LinkedList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.UUID; - - -public class QtBluetoothLE { - private static final String TAG = "QtBluetoothGatt"; - private final BluetoothAdapter mBluetoothAdapter; - private boolean mLeScanRunning = false; - - private BluetoothGatt mBluetoothGatt = null; - private String mRemoteGattAddress; - private final UUID clientCharacteristicUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - private final int MAX_MTU = 512; - private final int DEFAULT_MTU = 23; - private int mSupportedMtu = -1; - - /* - * The atomic synchronizes the timeoutRunnable thread and the response thread for the pending - * I/O job. Whichever thread comes first will pass the atomic gate. The other thread is - * cut short. - */ - // handle values above zero are for regular handle specific read/write requests - // handle values below zero are reserved for handle-independent requests - private int HANDLE_FOR_RESET = -1; - private int HANDLE_FOR_MTU_EXCHANGE = -2; - private AtomicInteger handleForTimeout = new AtomicInteger(HANDLE_FOR_RESET); // implies not running by default - - private final int RUNNABLE_TIMEOUT = 3000; // 3 seconds - private final Handler timeoutHandler = new Handler(Looper.getMainLooper()); - - /* New BTLE scanner setup since Android SDK v21 */ - private BluetoothLeScanner mBluetoothLeScanner = null; - - private class TimeoutRunnable implements Runnable { - public TimeoutRunnable(int handle) { pendingJobHandle = handle; } - @Override - public void run() { - boolean timeoutStillValid = handleForTimeout.compareAndSet(pendingJobHandle, HANDLE_FOR_RESET); - if (timeoutStillValid) { - Log.w(TAG, "****** Timeout for request on handle " + (pendingJobHandle & 0xffff)); - Log.w(TAG, "****** Looks like the peripheral does NOT act in " + - "accordance to Bluetooth 4.x spec."); - Log.w(TAG, "****** Please check server implementation. Continuing under " + - "reservation."); - - if (pendingJobHandle > HANDLE_FOR_RESET) - interruptCurrentIO(pendingJobHandle & 0xffff); - else if (pendingJobHandle < HANDLE_FOR_RESET) - interruptCurrentIO(pendingJobHandle); - } - } - - // contains handle (0xffff) and top 2 byte contain the job type (0xffff0000) - private int pendingJobHandle = -1; - }; - - - /* Pointer to the Qt object that "owns" the Java object */ - @SuppressWarnings({"CanBeFinal", "WeakerAccess"}) - long qtObject = 0; - @SuppressWarnings("WeakerAccess") - Context qtContext = null; - - @SuppressWarnings("WeakerAccess") - public QtBluetoothLE() { - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); - } - - public QtBluetoothLE(final String remoteAddress, Context context) { - this(); - qtContext = context; - mRemoteGattAddress = remoteAddress; - } - - /*************************************************************/ - /* Device scan */ - /*************************************************************/ - - /* - Returns true, if request was successfully completed - */ - public boolean scanForLeDevice(final boolean isEnabled) { - if (isEnabled == mLeScanRunning) - return true; - - if (isEnabled) { - Log.d(TAG, "New BTLE scanning API"); - ScanSettings.Builder settingsBuilder = new ScanSettings.Builder(); - settingsBuilder = settingsBuilder.setScanMode(ScanSettings.SCAN_MODE_BALANCED); - ScanSettings settings = settingsBuilder.build(); - - List<ScanFilter> filterList = new ArrayList<ScanFilter>(2); - - mBluetoothLeScanner.startScan(filterList, settings, leScanCallback21); - mLeScanRunning = true; - } else { - mBluetoothLeScanner.stopScan(leScanCallback21); - mLeScanRunning = false; - } - - return (mLeScanRunning == isEnabled); - } - - // Device scan callback (SDK v21+) - private final ScanCallback leScanCallback21 = new ScanCallback() { - @Override - public void onScanResult(int callbackType, ScanResult result) { - super.onScanResult(callbackType, result); - leScanResult(qtObject, result.getDevice(), result.getRssi(), result.getScanRecord().getBytes()); - } - - @Override - public void onBatchScanResults(List<ScanResult> results) { - super.onBatchScanResults(results); - for (ScanResult result : results) - leScanResult(qtObject, result.getDevice(), result.getRssi(), result.getScanRecord().getBytes()); - - } - - @Override - public void onScanFailed(int errorCode) { - super.onScanFailed(errorCode); - Log.d(TAG, "BTLE device scan failed with " + errorCode); - } - }; - - public native void leScanResult(long qtObject, BluetoothDevice device, int rssi, byte[] scanRecord); - - /*************************************************************/ - /* Service Discovery */ - /*************************************************************/ - - private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { - - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - if (qtObject == 0) - return; - - int qLowEnergyController_State = 0; - //This must be in sync with QLowEnergyController::ControllerState - switch (newState) { - case BluetoothProfile.STATE_DISCONNECTED: - qLowEnergyController_State = 0; - // we disconnected -> get rid of data from previous run - resetData(); - // reset mBluetoothGatt, reusing same object is not very reliable - // sometimes it reconnects and sometimes it does not. - if (mBluetoothGatt != null) - mBluetoothGatt.close(); - mBluetoothGatt = null; - break; - case BluetoothProfile.STATE_CONNECTED: - qLowEnergyController_State = 2; - } - - //This must be in sync with QLowEnergyController::Error - int errorCode; - switch (status) { - case BluetoothGatt.GATT_SUCCESS: - errorCode = 0; break; //QLowEnergyController::NoError - case BluetoothGatt.GATT_FAILURE: // Android's equivalent of "do not know what error it is" - errorCode = 1; break; //QLowEnergyController::UnknownError - case 8: // BLE_HCI_CONNECTION_TIMEOUT - Log.w(TAG, "Connection Error: Try to delay connect() call after previous activity"); - errorCode = 5; break; //QLowEnergyController::ConnectionError - case 19: // BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION - case 20: // BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_LOW_RESOURCES - case 21: // BLE_HCI_REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF - Log.w(TAG, "The remote host closed the connection"); - errorCode = 7; //QLowEnergyController::RemoteHostClosedError - break; - case 22: // BLE_HCI_LOCAL_HOST_TERMINATED_CONNECTION - // Internally, Android maps PIN_OR_KEY_MISSING to GATT_CONN_TERMINATE_LOCAL_HOST - errorCode = 8; break; //QLowEnergyController::AuthorizationError - default: - Log.w(TAG, "Unhandled error code on connectionStateChanged: " + status + " " + newState); - errorCode = status; break; //TODO deal with all errors - } - leConnectionStateChange(qtObject, errorCode, qLowEnergyController_State); - } - - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - //This must be in sync with QLowEnergyController::Error - int errorCode; - StringBuilder builder = new StringBuilder(); - switch (status) { - case BluetoothGatt.GATT_SUCCESS: - errorCode = 0; //QLowEnergyController::NoError - final List<BluetoothGattService> services = mBluetoothGatt.getServices(); - for (BluetoothGattService service: services) { - builder.append(service.getUuid().toString()).append(" "); //space is separator - } - break; - default: - Log.w(TAG, "Unhandled error code on onServicesDiscovered: " + status); - errorCode = status; break; //TODO deal with all errors - } - leServicesDiscovered(qtObject, errorCode, builder.toString()); - - scheduleMtuExchange(); - } - - public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt, - android.bluetooth.BluetoothGattCharacteristic characteristic, - int status) - { - int foundHandle = -1; - synchronized (this) { - foundHandle = handleForCharacteristic(characteristic); - if (foundHandle == -1 || foundHandle >= entries.size() ) { - Log.w(TAG, "Cannot find characteristic read request for read notification - handle: " + - foundHandle + " size: " + entries.size()); - - //unlock the queue for next item - synchronized (readWriteQueue) { - ioJobPending = false; - } - - performNextIO(); - return; - } - } - - boolean requestTimedOut = !handleForTimeout.compareAndSet( - modifiedReadWriteHandle(foundHandle, IoJobType.Read), HANDLE_FOR_RESET); - if (requestTimedOut) { - Log.w(TAG, "Late char read reply after timeout was hit for handle " + foundHandle); - // Timeout has hit before this response -> ignore the response - // no need to unlock ioJobPending -> the timeout has done that already - return; - } - - GattEntry entry = entries.get(foundHandle); - final boolean isServiceDiscoveryRun = !entry.valueKnown; - entry.valueKnown = true; - - if (status == BluetoothGatt.GATT_SUCCESS) { - // Qt manages handles starting at 1, in Java we use a system starting with 0 - //TODO avoid sending service uuid -> service handle should be sufficient - leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(), - foundHandle + 1, characteristic.getUuid().toString(), - characteristic.getProperties(), characteristic.getValue()); - } else { - if (isServiceDiscoveryRun) { - Log.w(TAG, "onCharacteristicRead during discovery error: " + status); - - Log.d(TAG, "Non-readable characteristic " + characteristic.getUuid() + - " for service " + characteristic.getService().getUuid()); - leCharacteristicRead(qtObject, characteristic.getService().getUuid().toString(), - foundHandle + 1, characteristic.getUuid().toString(), - characteristic.getProperties(), characteristic.getValue()); - } else { - // This must be in sync with QLowEnergyService::CharacteristicReadError - final int characteristicReadError = 5; - leServiceError(qtObject, foundHandle + 1, characteristicReadError); - } - } - - if (isServiceDiscoveryRun) { - - // last entry of pending service discovery run -> send discovery finished state update - GattEntry serviceEntry = entries.get(entry.associatedServiceHandle); - if (serviceEntry.endHandle == foundHandle) - finishCurrentServiceDiscovery(entry.associatedServiceHandle); - } - - //unlock the queue for next item - synchronized (readWriteQueue) { - ioJobPending = false; - } - - performNextIO(); - } - - public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt, - android.bluetooth.BluetoothGattCharacteristic characteristic, - int status) - { - if (status != BluetoothGatt.GATT_SUCCESS) - Log.w(TAG, "onCharacteristicWrite: error " + status); - - int handle = handleForCharacteristic(characteristic); - if (handle == -1) { - Log.w(TAG,"onCharacteristicWrite: cannot find handle"); - return; - } - - boolean requestTimedOut = !handleForTimeout.compareAndSet( - modifiedReadWriteHandle(handle, IoJobType.Write), HANDLE_FOR_RESET); - if (requestTimedOut) { - Log.w(TAG, "Late char write reply after timeout was hit for handle " + handle); - // Timeout has hit before this response -> ignore the response - // no need to unlock ioJobPending -> the timeout has done that already - return; - } - - int errorCode; - //This must be in sync with QLowEnergyService::ServiceError - switch (status) { - case BluetoothGatt.GATT_SUCCESS: - errorCode = 0; break; // NoError - default: - errorCode = 2; break; // CharacteristicWriteError - } - - synchronized (readWriteQueue) { - ioJobPending = false; - } - leCharacteristicWritten(qtObject, handle+1, characteristic.getValue(), errorCode); - performNextIO(); - } - - public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt, - android.bluetooth.BluetoothGattCharacteristic characteristic) - { - int handle = handleForCharacteristic(characteristic); - if (handle == -1) { - Log.w(TAG,"onCharacteristicChanged: cannot find handle"); - return; - } - - leCharacteristicChanged(qtObject, handle+1, characteristic.getValue()); - } - - public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt, - android.bluetooth.BluetoothGattDescriptor descriptor, - int status) - { - int foundHandle = -1; - synchronized (this) { - foundHandle = handleForDescriptor(descriptor); - if (foundHandle == -1 || foundHandle >= entries.size() ) { - Log.w(TAG, "Cannot find descriptor read request for read notification - handle: " + - foundHandle + " size: " + entries.size()); - - //unlock the queue for next item - synchronized (readWriteQueue) { - ioJobPending = false; - } - performNextIO(); - return; - } - } - - boolean requestTimedOut = !handleForTimeout.compareAndSet( - modifiedReadWriteHandle(foundHandle, IoJobType.Read), HANDLE_FOR_RESET); - if (requestTimedOut) { - Log.w(TAG, "Late descriptor read reply after timeout was hit for handle " + - foundHandle); - // Timeout has hit before this response -> ignore the response - // no need to unlock ioJobPending -> the timeout has done that already - return; - } - - GattEntry entry = entries.get(foundHandle); - final boolean isServiceDiscoveryRun = !entry.valueKnown; - entry.valueKnown = true; - - if (status == BluetoothGatt.GATT_SUCCESS) { - //TODO avoid sending service and characteristic uuid -> handles should be sufficient - leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(), - descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1, - descriptor.getUuid().toString(), descriptor.getValue()); - } else { - if (isServiceDiscoveryRun) { - // Cannot read but still advertise the fact that we found a descriptor - // The value will be empty. - Log.w(TAG, "onDescriptorRead during discovery error: " + status); - Log.d(TAG, "Non-readable descriptor " + descriptor.getUuid() + - " for characteristic " + descriptor.getCharacteristic().getUuid() + - " for service " + descriptor.getCharacteristic().getService().getUuid()); - leDescriptorRead(qtObject, descriptor.getCharacteristic().getService().getUuid().toString(), - descriptor.getCharacteristic().getUuid().toString(), foundHandle + 1, - descriptor.getUuid().toString(), descriptor.getValue()); - } else { - // This must be in sync with QLowEnergyService::DescriptorReadError - final int descriptorReadError = 6; - leServiceError(qtObject, foundHandle + 1, descriptorReadError); - } - - } - - if (isServiceDiscoveryRun) { - // last entry of pending service discovery run? ->send discovery finished state update - GattEntry serviceEntry = entries.get(entry.associatedServiceHandle); - if (serviceEntry.endHandle == foundHandle) { - finishCurrentServiceDiscovery(entry.associatedServiceHandle); - } - - /* Some devices preset ClientCharacteristicConfiguration descriptors - * to enable notifications out of the box. However the additional - * BluetoothGatt.setCharacteristicNotification call prevents - * automatic notifications from coming through. Hence we manually set them - * up here. - */ - if (descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) { - byte[] bytearray = descriptor.getValue(); - final int value = (bytearray != null && bytearray.length > 0) ? bytearray[0] : 0; - // notification or indication bit set? - if ((value & 0x03) > 0) { - Log.d(TAG, "Found descriptor with automatic notifications."); - mBluetoothGatt.setCharacteristicNotification( - descriptor.getCharacteristic(), true); - } - } - } - - //unlock the queue for next item - synchronized (readWriteQueue) { - ioJobPending = false; - } - - performNextIO(); - } - - public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt, - android.bluetooth.BluetoothGattDescriptor descriptor, - int status) - { - if (status != BluetoothGatt.GATT_SUCCESS) - Log.w(TAG, "onDescriptorWrite: error " + status); - - int handle = handleForDescriptor(descriptor); - - boolean requestTimedOut = !handleForTimeout.compareAndSet( - modifiedReadWriteHandle(handle, IoJobType.Write), HANDLE_FOR_RESET); - if (requestTimedOut) { - Log.w(TAG, "Late descriptor write reply after timeout was hit for handle " + - handle); - // Timeout has hit before this response -> ignore the response - // no need to unlock ioJobPending -> the timeout has done that already - return; - } - - int errorCode; - //This must be in sync with QLowEnergyService::ServiceError - switch (status) { - case BluetoothGatt.GATT_SUCCESS: - errorCode = 0; break; // NoError - default: - errorCode = 3; break; // DescriptorWriteError - } - - synchronized (readWriteQueue) { - ioJobPending = false; - } - - leDescriptorWritten(qtObject, handle+1, descriptor.getValue(), errorCode); - performNextIO(); - } - //TODO Requires Android API 21 which is not available on CI yet. -// public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt gatt, -// int status) { -// System.out.println("onReliableWriteCompleted"); -// } -// -// public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt, -// int rssi, int status) { -// System.out.println("onReadRemoteRssi"); -// } - - // requires Android API v21 - public void onMtuChanged(android.bluetooth.BluetoothGatt gatt, int mtu, int status) - { - if (status == BluetoothGatt.GATT_SUCCESS) { - Log.w(TAG, "MTU changed to " + mtu); - mSupportedMtu = mtu; - } else { - Log.w(TAG, "MTU change error " + status + ". New MTU " + mtu); - mSupportedMtu = DEFAULT_MTU; - } - - boolean requestTimedOut = !handleForTimeout.compareAndSet( - modifiedReadWriteHandle(HANDLE_FOR_MTU_EXCHANGE, IoJobType.Mtu), HANDLE_FOR_RESET); - if (requestTimedOut) { - Log.w(TAG, "Late mtu reply after timeout was hit"); - // Timeout has hit before this response -> ignore the response - // no need to unlock ioJobPending -> the timeout has done that already - return; - } - - synchronized (readWriteQueue) { - ioJobPending = false; - } - - performNextIO(); - } - }; - - - public boolean connect() { - BluetoothDevice mRemoteGattDevice; - - try { - mRemoteGattDevice = mBluetoothAdapter.getRemoteDevice(mRemoteGattAddress); - } catch (IllegalArgumentException ex) { - Log.w(TAG, "Remote address is not valid: " + mRemoteGattAddress); - return false; - } - - try { - // BluetoothDevice.connectGatt(Context, boolean, BluetoothGattCallback, int) was - // officially introduced by Android API v23. Earlier Android versions have a private - // implementation already though. Let's check at runtime and use it if possible. - // - // In general the new connectGatt() seems to be much more reliable than the function - // that doesn't specify the transport layer. - - Class[] args = new Class[4]; - args[0] = android.content.Context.class; - args[1] = boolean.class; - args[2] = android.bluetooth.BluetoothGattCallback.class; - args[3] = int.class; - Method connectMethod = mRemoteGattDevice.getClass().getDeclaredMethod("connectGatt", args); - if (connectMethod != null) { - mBluetoothGatt = (BluetoothGatt) connectMethod.invoke(mRemoteGattDevice, qtContext, - false, gattCallback, - 2 /*TRANSPORT_LE*/); - Log.w(TAG, "Using Android v23 BluetoothDevice.connectGatt()"); - } - } catch (Exception ex) { - // fallback to less reliable API 18 version - mBluetoothGatt = mRemoteGattDevice.connectGatt(qtContext, false, gattCallback); - } - - return mBluetoothGatt != null; - } - - public void disconnect() { - if (mBluetoothGatt == null) - return; - - mBluetoothGatt.disconnect(); - } - - public boolean discoverServices() - { - return mBluetoothGatt != null && mBluetoothGatt.discoverServices(); - } - - private enum GattEntryType - { - Service, Characteristic, CharacteristicValue, Descriptor - } - private class GattEntry - { - public GattEntryType type; - public boolean valueKnown = false; - public BluetoothGattService service = null; - public BluetoothGattCharacteristic characteristic = null; - public BluetoothGattDescriptor descriptor = null; - /* - * endHandle defined for GattEntryType.Service and GattEntryType.CharacteristicValue - * If the type is service this is the value of the last Gatt entry belonging to the very - * same service. If the type is a char value it is the entries index inside - * the "entries" list. - */ - public int endHandle = -1; - // pointer back to the handle that describes the service that this GATT entry belongs to - public int associatedServiceHandle; - } - - private enum IoJobType - { - Read, Write, Mtu - } - - private class ReadWriteJob - { - public GattEntry entry; - public byte[] newValue; - public int requestedWriteType; - public IoJobType jobType; - } - - // service uuid -> service handle mapping (there can be more than one service with same uuid) - private final Hashtable<UUID, List<Integer>> uuidToEntry = new Hashtable<UUID, List<Integer>>(100); - // index into array is equivalent to handle id - private final ArrayList<GattEntry> entries = new ArrayList<GattEntry>(100); - //backlog of to be discovered services - // TODO remove - private final LinkedList<Integer> servicesToBeDiscovered = new LinkedList<Integer>(); - - - private final LinkedList<ReadWriteJob> readWriteQueue = new LinkedList<ReadWriteJob>(); - private boolean ioJobPending; - - /* - Internal helper function - Returns the handle id for the given characteristic; otherwise returns -1. - - Note that this is the Java handle. The Qt handle is the Java handle +1. - */ - private int handleForCharacteristic(BluetoothGattCharacteristic characteristic) - { - if (characteristic == null) - return -1; - - List<Integer> handles = uuidToEntry.get(characteristic.getService().getUuid()); - if (handles == null || handles.isEmpty()) - return -1; - - //TODO for now we assume we always want the first service in case of uuid collision - int serviceHandle = handles.get(0); - - try { - GattEntry entry; - for (int i = serviceHandle+1; i < entries.size(); i++) { - entry = entries.get(i); - if (entry == null) - continue; - - switch (entry.type) { - case Descriptor: - case CharacteristicValue: - continue; - case Service: - break; - case Characteristic: - if (entry.characteristic == characteristic) - return i; - break; - } - } - } catch (IndexOutOfBoundsException ex) { /*nothing*/ } - return -1; - } - - /* - Internal helper function - Returns the handle id for the given descriptor; otherwise returns -1. - - Note that this is the Java handle. The Qt handle is the Java handle +1. - */ - private int handleForDescriptor(BluetoothGattDescriptor descriptor) - { - if (descriptor == null) - return -1; - - List<Integer> handles = uuidToEntry.get(descriptor.getCharacteristic().getService().getUuid()); - if (handles == null || handles.isEmpty()) - return -1; - - //TODO for now we assume we always want the first service in case of uuid collision - int serviceHandle = handles.get(0); - - try { - GattEntry entry; - for (int i = serviceHandle+1; i < entries.size(); i++) { - entry = entries.get(i); - if (entry == null) - continue; - - switch (entry.type) { - case Characteristic: - case CharacteristicValue: - continue; - case Service: - break; - case Descriptor: - if (entry.descriptor == descriptor) - return i; - break; - } - } - } catch (IndexOutOfBoundsException ignored) { } - return -1; - } - - private void populateHandles() - { - // We introduce the notion of artificial handles. While GATT handles - // are not exposed on Android they help to quickly identify GATT attributes - // on the C++ side. The Qt Api will not expose the handles - GattEntry entry = null; - List<BluetoothGattService> services = mBluetoothGatt.getServices(); - for (BluetoothGattService service: services) { - GattEntry serviceEntry = new GattEntry(); - serviceEntry.type = GattEntryType.Service; - serviceEntry.service = service; - entries.add(serviceEntry); - - // remember handle for the service for later update - int serviceHandle = entries.size() - 1; - //point to itself -> mostly done for consistence reasons with other entries - serviceEntry.associatedServiceHandle = serviceHandle; - - //some devices may have more than one service with the same uuid - List<Integer> old = uuidToEntry.get(service.getUuid()); - if (old == null) - old = new ArrayList<Integer>(); - old.add(entries.size()-1); - uuidToEntry.put(service.getUuid(), old); - - // add all characteristics - List<BluetoothGattCharacteristic> charList = service.getCharacteristics(); - for (BluetoothGattCharacteristic characteristic: charList) { - entry = new GattEntry(); - entry.type = GattEntryType.Characteristic; - entry.characteristic = characteristic; - entry.associatedServiceHandle = serviceHandle; - //entry.endHandle = .. undefined - entries.add(entry); - - // this emulates GATT value attributes - entry = new GattEntry(); - entry.type = GattEntryType.CharacteristicValue; - entry.associatedServiceHandle = serviceHandle; - entry.endHandle = entries.size(); // special case -> current index in entries list - entries.add(entry); - - // add all descriptors - List<BluetoothGattDescriptor> descList = characteristic.getDescriptors(); - for (BluetoothGattDescriptor desc: descList) { - entry = new GattEntry(); - entry.type = GattEntryType.Descriptor; - entry.descriptor = desc; - entry.associatedServiceHandle = serviceHandle; - //entry.endHandle = .. undefined - entries.add(entry); - } - } - - // update endHandle of current service - serviceEntry.endHandle = entries.size() - 1; - } - - entries.trimToSize(); - } - - private void resetData() - { - synchronized (this) { - uuidToEntry.clear(); - entries.clear(); - servicesToBeDiscovered.clear(); - } - - // kill all timeout handlers - timeoutHandler.removeCallbacksAndMessages(null); - handleForTimeout.set(HANDLE_FOR_RESET); - - synchronized (readWriteQueue) { - readWriteQueue.clear(); - } - } - - public synchronized boolean discoverServiceDetails(String serviceUuid) - { - try { - if (mBluetoothGatt == null) - return false; - - if (entries.isEmpty()) - populateHandles(); - - GattEntry entry; - int serviceHandle; - try { - UUID service = UUID.fromString(serviceUuid); - List<Integer> handles = uuidToEntry.get(service); - if (handles == null || handles.isEmpty()) { - Log.w(TAG, "Unknown service uuid for current device: " + service.toString()); - return false; - } - - //TODO for now we assume we always want the first service in case of uuid collision - serviceHandle = handles.get(0); - entry = entries.get(serviceHandle); - if (entry == null) { - Log.w(TAG, "Service with UUID " + service.toString() + " not found"); - return false; - } - } catch (IllegalArgumentException ex) { - //invalid UUID string passed - Log.w(TAG, "Cannot parse given UUID"); - return false; - } - - if (entry.type != GattEntryType.Service) { - Log.w(TAG, "Given UUID is not a service UUID: " + serviceUuid); - return false; - } - - // current service already discovered or under investigation - if (entry.valueKnown || servicesToBeDiscovered.contains(serviceHandle)) { - Log.w(TAG, "Service already known or to be discovered"); - return true; - } - - servicesToBeDiscovered.add(serviceHandle); - scheduleServiceDetailDiscovery(serviceHandle); - performNextIO(); - - } catch (Exception ex) { - ex.printStackTrace(); - return false; - } - - return true; - } - - /* - Returns the uuids of the services included by the given service. Otherwise returns null. - Directly called from Qt. - */ - public String includedServices(String serviceUuid) - { - if (mBluetoothGatt == null) - return null; - - UUID uuid; - try { - uuid = UUID.fromString(serviceUuid); - } catch (Exception ex) { - ex.printStackTrace(); - return null; - } - - //TODO Breaks in case of two services with same uuid - BluetoothGattService service = mBluetoothGatt.getService(uuid); - if (service == null) - return null; - - final List<BluetoothGattService> includes = service.getIncludedServices(); - if (includes.isEmpty()) - return null; - - StringBuilder builder = new StringBuilder(); - for (BluetoothGattService includedService: includes) { - builder.append(includedService.getUuid().toString()).append(" "); //space is separator - } - - return builder.toString(); - } - - //TODO function not yet used - private void finishCurrentServiceDiscovery(int handleDiscoveredService) - { - Log.w(TAG, "Finished current discovery for service handle " + handleDiscoveredService); - GattEntry discoveredService = entries.get(handleDiscoveredService); - discoveredService.valueKnown = true; - synchronized (this) { - try { - servicesToBeDiscovered.removeFirst(); - } catch (NoSuchElementException ex) { - Log.w(TAG, "Expected queued service but didn't find any"); - } - } - - leServiceDetailDiscoveryFinished(qtObject, discoveredService.service.getUuid().toString(), - handleDiscoveredService + 1, discoveredService.endHandle + 1); - } - - private boolean executeMtuExchange() - { - if (Build.VERSION.SDK_INT >= 21) { - try { - Method mtuMethod = mBluetoothGatt.getClass().getDeclaredMethod("requestMtu", int.class); - if (mtuMethod != null) { - Boolean success = (Boolean) mtuMethod.invoke(mBluetoothGatt, MAX_MTU); - if (success.booleanValue()) { - Log.w(TAG, "MTU change initiated"); - return false; - } else { - Log.w(TAG, "MTU change request failed"); - } - } - } catch (Exception ex) {} - } - - Log.w(TAG, "Assuming default MTU value of 23 bytes"); - - mSupportedMtu = DEFAULT_MTU; - return true; - } - - private void scheduleMtuExchange() - { - ReadWriteJob newJob = new ReadWriteJob(); - newJob.jobType = IoJobType.Mtu; - newJob.entry = null; - - synchronized (readWriteQueue) { - readWriteQueue.add(newJob); - } - - performNextIO(); - } - - /* - Internal Helper function for discoverServiceDetails() - - Adds all Gatt entries for the given service to the readWriteQueue to be discovered. - This function only ever adds read requests to the queue. - - //TODO function not yet used - */ - private void scheduleServiceDetailDiscovery(int serviceHandle) - { - GattEntry serviceEntry = entries.get(serviceHandle); - final int endHandle = serviceEntry.endHandle; - - if (serviceHandle == endHandle) { - Log.w(TAG, "scheduleServiceDetailDiscovery: service is empty; nothing to discover"); - finishCurrentServiceDiscovery(serviceHandle); - return; - } - - synchronized (readWriteQueue) { - // entire block inside mutex to ensure all service discovery jobs go in one after the other - // ensures that serviceDiscovered() signal is sent when required - - - // serviceHandle + 1 -> ignore service handle itself - for (int i = serviceHandle + 1; i <= endHandle; i++) { - GattEntry entry = entries.get(i); - - switch (entry.type) { - case Characteristic: - case Descriptor: - // we schedule CharacteristicValue for initial discovery to simplify - // detection of the end of service discovery process - // performNextIO() ignores CharacteristicValue GATT entries - case CharacteristicValue: - break; - case Service: - // should not really happen unless endHandle is wrong - Log.w(TAG, "scheduleServiceDetailDiscovery: wrong endHandle"); - return; - } - - // only descriptor and characteristic fall through to this point - ReadWriteJob newJob = new ReadWriteJob(); - newJob.entry = entry; - newJob.jobType = IoJobType.Read; - - final boolean result = readWriteQueue.add(newJob); - if (!result) - Log.w(TAG, "Cannot add service discovery job for " + serviceEntry.service.getUuid() - + " on item " + entry.type); - } - } - } - - /*************************************************************/ - /* Write Characteristics */ - /*************************************************************/ - - public boolean writeCharacteristic(int charHandle, byte[] newValue, - int writeMode) - { - if (mBluetoothGatt == null) - return false; - - GattEntry entry; - try { - entry = entries.get(charHandle-1); //Qt always uses handles+1 - } catch (IndexOutOfBoundsException ex) { - ex.printStackTrace(); - return false; - } - - ReadWriteJob newJob = new ReadWriteJob(); - newJob.newValue = newValue; - newJob.entry = entry; - newJob.jobType = IoJobType.Write; - - // writeMode must be in sync with QLowEnergyService::WriteMode - switch (writeMode) { - case 1: //WriteWithoutResponse - newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE; - break; - case 2: //WriteSigned - newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_SIGNED; - break; - default: - newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT; - break; - } - - boolean result; - synchronized (readWriteQueue) { - result = readWriteQueue.add(newJob); - } - - if (!result) { - Log.w(TAG, "Cannot add characteristic write request for " + charHandle + " to queue" ); - return false; - } - - performNextIO(); - return true; - } - - /*************************************************************/ - /* Write Descriptors */ - /*************************************************************/ - - public boolean writeDescriptor(int descHandle, byte[] newValue) - { - if (mBluetoothGatt == null) - return false; - - GattEntry entry; - try { - entry = entries.get(descHandle-1); //Qt always uses handles+1 - } catch (IndexOutOfBoundsException ex) { - ex.printStackTrace(); - return false; - } - - ReadWriteJob newJob = new ReadWriteJob(); - newJob.newValue = newValue; - newJob.entry = entry; - newJob.requestedWriteType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT; - newJob.jobType = IoJobType.Write; - - boolean result; - synchronized (readWriteQueue) { - result = readWriteQueue.add(newJob); - } - - if (!result) { - Log.w(TAG, "Cannot add descriptor write request for " + descHandle + " to queue" ); - return false; - } - - performNextIO(); - return true; - } - - /*************************************************************/ - /* Read Characteristics */ - /*************************************************************/ - - public boolean readCharacteristic(int charHandle) - { - if (mBluetoothGatt == null) - return false; - - GattEntry entry; - try { - entry = entries.get(charHandle-1); //Qt always uses handles+1 - } catch (IndexOutOfBoundsException ex) { - ex.printStackTrace(); - return false; - } - - ReadWriteJob newJob = new ReadWriteJob(); - newJob.entry = entry; - newJob.jobType = IoJobType.Read; - - boolean result; - synchronized (readWriteQueue) { - result = readWriteQueue.add(newJob); - } - - if (!result) { - Log.w(TAG, "Cannot add characteristic read request for " + charHandle + " to queue" ); - return false; - } - - performNextIO(); - return true; - } - - public boolean readDescriptor(int descHandle) - { - if (mBluetoothGatt == null) - return false; - - GattEntry entry; - try { - entry = entries.get(descHandle-1); //Qt always uses handles+1 - } catch (IndexOutOfBoundsException ex) { - ex.printStackTrace(); - return false; - } - - ReadWriteJob newJob = new ReadWriteJob(); - newJob.entry = entry; - newJob.jobType = IoJobType.Read; - - boolean result; - synchronized (readWriteQueue) { - result = readWriteQueue.add(newJob); - } - - if (!result) { - Log.w(TAG, "Cannot add descriptor read request for " + descHandle + " to queue" ); - return false; - } - - performNextIO(); - return true; - } - - // Called by TimeoutRunnable if the current I/O job timed out. - // By the time we reach this point the handleForTimeout counter has already been reset - // and the regular responses will be blocked off. - private void interruptCurrentIO(int handle) - { - //unlock the queue for next item - synchronized (readWriteQueue) { - ioJobPending = false; - } - - performNextIO(); - - if (handle == HANDLE_FOR_MTU_EXCHANGE) - return; - - try { - synchronized (this) { - - GattEntry entry = entries.get(handle); - if (entry == null) - return; - if (entry.valueKnown) - return; - entry.valueKnown = true; - - GattEntry serviceEntry = entries.get(entry.associatedServiceHandle); - if (serviceEntry != null && serviceEntry.endHandle == handle) - finishCurrentServiceDiscovery(entry.associatedServiceHandle); - } - } catch (IndexOutOfBoundsException outOfBounds) { - Log.w(TAG, "interruptCurrentIO(): Unknown gatt entry, index: " - + handle + " size: " + entries.size()); - } - } - - /* - The queuing is required because two writeCharacteristic/writeDescriptor calls - cannot execute at the same time. The second write must happen after the - previous write has finished with on(Characteristic|Descriptor)Write(). - */ - private void performNextIO() - { - if (mBluetoothGatt == null) - return; - - boolean skip = false; - final ReadWriteJob nextJob; - int handle = HANDLE_FOR_RESET; - - synchronized (readWriteQueue) { - if (readWriteQueue.isEmpty() || ioJobPending) - return; - - nextJob = readWriteQueue.remove(); - if (nextJob.jobType == IoJobType.Mtu) { - handle = HANDLE_FOR_MTU_EXCHANGE; //mtu request is special case - } else { - switch (nextJob.entry.type) { - case Characteristic: - handle = handleForCharacteristic(nextJob.entry.characteristic); - break; - case Descriptor: - handle = handleForDescriptor(nextJob.entry.descriptor); - break; - case CharacteristicValue: - handle = nextJob.entry.endHandle; - default: - break; - } - } - - // timeout handler and handleForTimeout atomic must be setup before - // executing the request. Sometimes the callback is quicker than executing the - // remainder of this function. Therefore enable the atomic early such that - // callback handlers start hanging in the readWriteQueue sync block which - // we are still occupying here. - timeoutHandler.removeCallbacksAndMessages(null); // remove any timeout handlers - handleForTimeout.set(modifiedReadWriteHandle(handle, nextJob.jobType)); - - switch (nextJob.jobType) { - case Read: - skip = executeReadJob(nextJob); - break; - case Write: - skip = executeWriteJob(nextJob); - break; - case Mtu: - skip = executeMtuExchange(); - break; - } - - if (skip) { - handleForTimeout.set(HANDLE_FOR_RESET); // not a pending call -> release atomic - } else { - ioJobPending = true; - timeoutHandler.postDelayed(new TimeoutRunnable( - modifiedReadWriteHandle(handle, nextJob.jobType)), RUNNABLE_TIMEOUT); - } - - if (nextJob.jobType != IoJobType.Mtu) { - Log.w(TAG, "Performing queued job, handle: " + handle + " " + nextJob.jobType + " (" + - (nextJob.requestedWriteType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) + - ") ValueKnown: " + nextJob.entry.valueKnown + " Skipping: " + skip + - " " + nextJob.entry.type); - } - } - - GattEntry entry = nextJob.entry; - - if (skip) { - /* - BluetoothGatt.[read|write][Characteristic|Descriptor]() immediately - return in cases where meta data doesn't match the intended action - (e.g. trying to write to read-only char). When this happens - we have to report an error back to Qt. The error report is not required during - the initial service discovery though. - */ - if (handle > HANDLE_FOR_RESET) { - // during service discovery we do not report error but emit characteristicRead() - // any other time a failure emits serviceError() signal - - final boolean isServiceDiscovery = !entry.valueKnown; - - if (isServiceDiscovery) { - entry.valueKnown = true; - switch (entry.type) { - case Characteristic: - Log.d(TAG, "Non-readable characteristic " + entry.characteristic.getUuid() + - " for service " + entry.characteristic.getService().getUuid()); - leCharacteristicRead(qtObject, entry.characteristic.getService().getUuid().toString(), - handle + 1, entry.characteristic.getUuid().toString(), - entry.characteristic.getProperties(), entry.characteristic.getValue()); - break; - case Descriptor: - // atm all descriptor types are readable - Log.d(TAG, "Non-readable descriptor " + entry.descriptor.getUuid() + - " for service/char" + entry.descriptor.getCharacteristic().getService().getUuid() + - "/" + entry.descriptor.getCharacteristic().getUuid()); - leDescriptorRead(qtObject, - entry.descriptor.getCharacteristic().getService().getUuid().toString(), - entry.descriptor.getCharacteristic().getUuid().toString(), - handle + 1, entry.descriptor.getUuid().toString(), - entry.descriptor.getValue()); - break; - case CharacteristicValue: - // for more details see scheduleServiceDetailDiscovery(int) - break; - case Service: - Log.w(TAG, "Scheduling of Service Gatt entry for service discovery should never happen."); - break; - } - - // last entry of current discovery run? - synchronized (this) { - try { - GattEntry serviceEntry = entries.get(entry.associatedServiceHandle); - if (serviceEntry.endHandle == handle) - finishCurrentServiceDiscovery(entry.associatedServiceHandle); - } catch (IndexOutOfBoundsException outOfBounds) { - Log.w(TAG, "performNextIO(): Unknown service for entry, index: " - + entry.associatedServiceHandle + " size: " + entries.size()); - } - } - } else { - int errorCode = 0; - - // The error codes below must be in sync with QLowEnergyService::ServiceError - if (nextJob.jobType == IoJobType.Read) { - errorCode = (entry.type == GattEntryType.Characteristic) ? - 5 : 6; // CharacteristicReadError : DescriptorReadError - } else { - errorCode = (entry.type == GattEntryType.Characteristic) ? - 2 : 3; // CharacteristicWriteError : DescriptorWriteError - } - - leServiceError(qtObject, handle + 1, errorCode); - } - } - - performNextIO(); - } - } - - // Runs inside the Mutex on readWriteQueue. - // Returns true if nextJob should be skipped. - private boolean executeWriteJob(ReadWriteJob nextJob) - { - boolean result; - switch (nextJob.entry.type) { - case Characteristic: - if (nextJob.entry.characteristic.getWriteType() != nextJob.requestedWriteType) { - nextJob.entry.characteristic.setWriteType(nextJob.requestedWriteType); - } - result = nextJob.entry.characteristic.setValue(nextJob.newValue); - if (!result || !mBluetoothGatt.writeCharacteristic(nextJob.entry.characteristic)) - return true; - break; - case Descriptor: - if (nextJob.entry.descriptor.getUuid().compareTo(clientCharacteristicUuid) == 0) { - /* - For some reason, Android splits characteristic notifications - into two operations. BluetoothGatt.enableCharacteristicNotification - ensures the local Bluetooth stack forwards the notifications. In addition, - BluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) - must be written to the peripheral. - */ - - - /* There is no documentation on indication behavior. The assumption is - that when indication or notification are requested we call - BluetoothGatt.setCharacteristicNotification. Furthermore it is assumed - indications are send via onCharacteristicChanged too and Android itself - will do the confirmation required for an indication as per - Bluetooth spec Vol 3, Part G, 4.11 . If neither of the two bits are set - we disable the signals. - */ - boolean enableNotifications = false; - int value = (nextJob.newValue[0] & 0xff); - // first or second bit must be set - if (((value & 0x1) == 1) || (((value >> 1) & 0x1) == 1)) { - enableNotifications = true; - } - - result = mBluetoothGatt.setCharacteristicNotification( - nextJob.entry.descriptor.getCharacteristic(), enableNotifications); - if (!result) { - Log.w(TAG, "Cannot set characteristic notification"); - //we continue anyway to ensure that we write the requested value - //to the device - } - - Log.d(TAG, "Enable notifications: " + enableNotifications); - } - - result = nextJob.entry.descriptor.setValue(nextJob.newValue); - if (!result || !mBluetoothGatt.writeDescriptor(nextJob.entry.descriptor)) - return true; - break; - case Service: - case CharacteristicValue: - return true; - } - return false; - } - - // Runs inside the Mutex on readWriteQueue. - // Returns true if nextJob should be skipped. - private boolean executeReadJob(ReadWriteJob nextJob) - { - boolean result; - switch (nextJob.entry.type) { - case Characteristic: - try { - result = mBluetoothGatt.readCharacteristic(nextJob.entry.characteristic); - } catch (java.lang.SecurityException se) { - // QTBUG-59917 -> HID services cause problems since Android 5.1 - se.printStackTrace(); - result = false; - } - if (!result) - return true; // skip - break; - case Descriptor: - try { - result = mBluetoothGatt.readDescriptor(nextJob.entry.descriptor); - } catch (java.lang.SecurityException se) { - // QTBUG-59917 -> HID services cause problems since Android 5.1 - se.printStackTrace(); - result = false; - } - if (!result) - return true; // skip - break; - case Service: - return true; - case CharacteristicValue: - return true; //skip - } - return false; - } - - /* - * Modifies and returns the given \a handle such that the job - * \a type is encoded into the returned handle. Hereby we take advantage of the fact that - * a Bluetooth Low Energy handle is only 16 bit. The handle will be the bottom two bytes - * and the job type will be in the top 2 bytes. - * - * top 2 bytes - * - 0x01 -> Read Job - * - 0x02 -> Write Job - * - * This is done in connection with handleForTimeout and assists in the process of - * detecting accidental interruption by the timeout handler. - * If two requests for the same handle are scheduled behind each other there is the - * theoretical chance that the first request comes back normally while the second request - * is interrupted by the timeout handler. This risk still exists but this function ensures that - * at least back to back requests of differing types cannot affect each other via the timeout - * handler. - */ - private int modifiedReadWriteHandle(int handle, IoJobType type) - { - int modifiedHandle = handle; - // ensure we have 16bit handle only - if (handle > 0xFFFF) - Log.w(TAG, "Invalid handle"); - - modifiedHandle = (modifiedHandle & 0xFFFF); - - switch (type) { - case Write: - modifiedHandle = (modifiedHandle | 0x00010000); - break; - case Read: - modifiedHandle = (modifiedHandle | 0x00020000); - break; - case Mtu: - modifiedHandle = HANDLE_FOR_MTU_EXCHANGE; - break; - } - - return modifiedHandle; - } - - // Directly called from public Qt API - public boolean requestConnectionUpdatePriority(double minimalInterval) - { - if (mBluetoothGatt == null) - return false; - - try { - //Android API v21 - Method connectionUpdateMethod = mBluetoothGatt.getClass().getDeclaredMethod( - "requestConnectionPriority", int.class); - if (connectionUpdateMethod == null) - return false; - - int requestPriority = 0; // BluetoothGatt.CONNECTION_PRIORITY_BALANCED - if (minimalInterval < 30) - requestPriority = 1; // BluetoothGatt.CONNECTION_PRIORITY_HIGH - else if (minimalInterval > 100) - requestPriority = 2; //BluetoothGatt/CONNECTION_PRIORITY_LOW_POWER - - Object result = connectionUpdateMethod.invoke(mBluetoothGatt, requestPriority); - return (Boolean) result; - } catch (Exception ex) { - return false; - } - } - - public native void leConnectionStateChange(long qtObject, int wasErrorTransition, int newState); - public native void leServicesDiscovered(long qtObject, int errorCode, String uuidList); - public native void leServiceDetailDiscoveryFinished(long qtObject, final String serviceUuid, - int startHandle, int endHandle); - public native void leCharacteristicRead(long qtObject, String serviceUuid, - int charHandle, String charUuid, - int properties, byte[] data); - public native void leDescriptorRead(long qtObject, String serviceUuid, String charUuid, - int descHandle, String descUuid, byte[] data); - public native void leCharacteristicWritten(long qtObject, int charHandle, byte[] newData, - int errorCode); - public native void leDescriptorWritten(long qtObject, int charHandle, byte[] newData, - int errorCode); - public native void leCharacteristicChanged(long qtObject, int charHandle, byte[] newData); - public native void leServiceError(long qtObject, int attributeHandle, int errorCode); -} - |