mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-07-09 23:21:34 +02:00
Allow for support classes to send a write request response to the device, fi requested. The standard actually expects this to happen, but Gadgetbridge did not originally support it, so there are concerns that enabling this globally will cause issues for devices that do not expect the response. See also: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2831#issuecomment-941568
800 lines
34 KiB
Java
800 lines
34 KiB
Java
/* Copyright (C) 2015-2021 Andreas Böhler, Andreas Shimokawa, Carsten
|
|
Pfeiffer, Cre3per, Daniel Dakhno, Daniele Gobbetti, Sergey Trofimov, Taavi
|
|
Eomäe, Uwe Hermann
|
|
|
|
This file is part of Gadgetbridge.
|
|
|
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published
|
|
by the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Gadgetbridge is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
|
|
|
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.BluetoothGattServer;
|
|
import android.bluetooth.BluetoothGattServerCallback;
|
|
import android.bluetooth.BluetoothGattService;
|
|
import android.bluetooth.BluetoothManager;
|
|
import android.bluetooth.BluetoothProfile;
|
|
import android.content.Context;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.concurrent.BlockingQueue;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
|
|
|
|
/**
|
|
* One queue/thread per connectable device.
|
|
*/
|
|
public final class BtLEQueue {
|
|
private static final Logger LOG = LoggerFactory.getLogger(BtLEQueue.class);
|
|
|
|
private final Object mGattMonitor = new Object();
|
|
private final GBDevice mGbDevice;
|
|
private final BluetoothAdapter mBluetoothAdapter;
|
|
private BluetoothGatt mBluetoothGatt;
|
|
private BluetoothGattServer mBluetoothGattServer;
|
|
private final Set<BluetoothGattService> mSupportedServerServices;
|
|
|
|
private final BlockingQueue<AbstractTransaction> mTransactions = new LinkedBlockingQueue<>();
|
|
private volatile boolean mDisposed;
|
|
private volatile boolean mCrashed;
|
|
private volatile boolean mAbortTransaction;
|
|
private volatile boolean mAbortServerTransaction;
|
|
private volatile boolean mPauseTransaction = false;
|
|
|
|
private final Context mContext;
|
|
private CountDownLatch mWaitForActionResultLatch;
|
|
private CountDownLatch mWaitForServerActionResultLatch;
|
|
private CountDownLatch mConnectionLatch;
|
|
private BluetoothGattCharacteristic mWaitCharacteristic;
|
|
private final InternalGattCallback internalGattCallback;
|
|
private final InternalGattServerCallback internalGattServerCallback;
|
|
private boolean mAutoReconnect;
|
|
private boolean mImplicitGattCallbackModify = true;
|
|
private boolean mSendWriteRequestResponse = false;
|
|
|
|
private Thread dispatchThread = new Thread("Gadgetbridge GATT Dispatcher") {
|
|
|
|
@Override
|
|
public void run() {
|
|
LOG.debug("Queue Dispatch Thread started.");
|
|
|
|
while (!mDisposed && !mCrashed) {
|
|
try {
|
|
AbstractTransaction qTransaction = mTransactions.take();
|
|
|
|
if (!isConnected()) {
|
|
LOG.debug("not connected, waiting for connection...");
|
|
// TODO: request connection and initialization from the outside and wait until finished
|
|
internalGattCallback.reset();
|
|
|
|
// wait until the connection succeeds before running the actions
|
|
// Note that no automatic connection is performed. This has to be triggered
|
|
// on the outside typically by the DeviceSupport. The reason is that
|
|
// devices have different kinds of initializations and this class has no
|
|
// idea about them.
|
|
mConnectionLatch = new CountDownLatch(1);
|
|
mConnectionLatch.await();
|
|
mConnectionLatch = null;
|
|
}
|
|
|
|
if(qTransaction instanceof ServerTransaction) {
|
|
ServerTransaction serverTransaction = (ServerTransaction)qTransaction;
|
|
internalGattServerCallback.setTransactionGattCallback(serverTransaction.getGattCallback());
|
|
mAbortServerTransaction = false;
|
|
|
|
for (BtLEServerAction action : serverTransaction.getActions()) {
|
|
if (mAbortServerTransaction) { // got disconnected
|
|
LOG.info("Aborting running transaction");
|
|
break;
|
|
}
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("About to run server action: " + action);
|
|
}
|
|
if (action.run(mBluetoothGattServer)) {
|
|
// check again, maybe due to some condition, action did not need to write, so we can't wait
|
|
boolean waitForResult = action.expectsResult();
|
|
if (waitForResult) {
|
|
mWaitForServerActionResultLatch.await();
|
|
mWaitForServerActionResultLatch = null;
|
|
if (mAbortServerTransaction) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
LOG.error("Action returned false: " + action);
|
|
break; // abort the transaction
|
|
}
|
|
}
|
|
}
|
|
|
|
if(qTransaction instanceof Transaction) {
|
|
Transaction transaction = (Transaction)qTransaction;
|
|
LOG.trace("Changing gatt callback for {}? {}", transaction.getTaskName(), transaction.isModifyGattCallback());
|
|
if (mImplicitGattCallbackModify || transaction.isModifyGattCallback()) {
|
|
internalGattCallback.setTransactionGattCallback(transaction.getGattCallback());
|
|
}
|
|
mAbortTransaction = false;
|
|
// Run all actions of the transaction until one doesn't succeed
|
|
for (BtLEAction action : transaction.getActions()) {
|
|
if (mAbortTransaction) { // got disconnected
|
|
LOG.info("Aborting running transaction");
|
|
break;
|
|
}
|
|
while ((action instanceof WriteAction) && mPauseTransaction && !mAbortTransaction) {
|
|
LOG.info("Pausing WriteAction");
|
|
try {
|
|
Thread.sleep(100);
|
|
} catch (Exception e) {
|
|
LOG.info("Exception during pause: "+e.toString());
|
|
break;
|
|
}
|
|
}
|
|
mWaitCharacteristic = action.getCharacteristic();
|
|
mWaitForActionResultLatch = new CountDownLatch(1);
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("About to run action: " + action);
|
|
}
|
|
if (action instanceof GattListenerAction) {
|
|
// this special action overwrites the transaction gatt listener (if any), it must
|
|
// always be the last action in the transaction
|
|
internalGattCallback.setTransactionGattCallback(((GattListenerAction) action).getGattCallback());
|
|
}
|
|
if (action.run(mBluetoothGatt)) {
|
|
// check again, maybe due to some condition, action did not need to write, so we can't wait
|
|
boolean waitForResult = action.expectsResult();
|
|
if (waitForResult) {
|
|
mWaitForActionResultLatch.await();
|
|
mWaitForActionResultLatch = null;
|
|
if (mAbortTransaction) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
LOG.error("Action returned false: " + action);
|
|
break; // abort the transaction
|
|
}
|
|
}
|
|
}
|
|
} catch (InterruptedException ignored) {
|
|
mConnectionLatch = null;
|
|
LOG.debug("Thread interrupted");
|
|
} catch (Throwable ex) {
|
|
LOG.error("Queue Dispatch Thread died: " + ex.getMessage(), ex);
|
|
mCrashed = true;
|
|
mConnectionLatch = null;
|
|
} finally {
|
|
mWaitForActionResultLatch = null;
|
|
mWaitCharacteristic = null;
|
|
}
|
|
}
|
|
LOG.info("Queue Dispatch Thread terminated.");
|
|
}
|
|
};
|
|
|
|
public BtLEQueue(BluetoothAdapter bluetoothAdapter, GBDevice gbDevice, GattCallback externalGattCallback, GattServerCallback externalGattServerCallback, Context context, Set<BluetoothGattService> supportedServerServices) {
|
|
mBluetoothAdapter = bluetoothAdapter;
|
|
mGbDevice = gbDevice;
|
|
internalGattCallback = new InternalGattCallback(externalGattCallback);
|
|
internalGattServerCallback = new InternalGattServerCallback(externalGattServerCallback);
|
|
mContext = context;
|
|
mSupportedServerServices = supportedServerServices;
|
|
|
|
dispatchThread.start();
|
|
}
|
|
|
|
public void setAutoReconnect(boolean enable) {
|
|
mAutoReconnect = enable;
|
|
}
|
|
|
|
public void setImplicitGattCallbackModify(final boolean enable) {
|
|
mImplicitGattCallbackModify = enable;
|
|
}
|
|
|
|
public void setSendWriteRequestResponse(final boolean enable) {
|
|
mSendWriteRequestResponse = enable;
|
|
}
|
|
|
|
protected boolean isConnected() {
|
|
return mGbDevice.isConnected();
|
|
}
|
|
|
|
/**
|
|
* Connects to the given remote device. Note that this does not perform any device
|
|
* specific initialization. This should be done in the specific {@link DeviceSupport}
|
|
* class.
|
|
*
|
|
* @return <code>true</code> whether the connection attempt was successfully triggered and <code>false</code> if that failed or if there is already a connection
|
|
*/
|
|
public boolean connect() {
|
|
mPauseTransaction = false;
|
|
if (isConnected()) {
|
|
LOG.warn("Ingoring connect() because already connected.");
|
|
return false;
|
|
}
|
|
synchronized (mGattMonitor) {
|
|
if (mBluetoothGatt != null) {
|
|
// Tribal knowledge says you're better off not reusing existing BluetoothGatt connections,
|
|
// so create a new one.
|
|
LOG.info("connect() requested -- disconnecting previous connection: " + mGbDevice.getName());
|
|
disconnect();
|
|
}
|
|
}
|
|
LOG.info("Attempting to connect to " + mGbDevice.getName());
|
|
mBluetoothAdapter.cancelDiscovery();
|
|
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
|
|
if(!mSupportedServerServices.isEmpty()) {
|
|
BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
|
if (bluetoothManager == null) {
|
|
LOG.error("Error getting bluetoothManager");
|
|
return false;
|
|
}
|
|
mBluetoothGattServer = bluetoothManager.openGattServer(mContext, internalGattServerCallback);
|
|
if (mBluetoothGattServer == null) {
|
|
LOG.error("Error opening Gatt Server");
|
|
return false;
|
|
}
|
|
for(BluetoothGattService service : mSupportedServerServices) {
|
|
mBluetoothGattServer.addService(service);
|
|
}
|
|
}
|
|
|
|
synchronized (mGattMonitor) {
|
|
// connectGatt with true doesn't really work ;( too often connection problems
|
|
if (GBApplication.isRunningMarshmallowOrLater()) {
|
|
mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback, BluetoothDevice.TRANSPORT_LE);
|
|
} else {
|
|
mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback);
|
|
}
|
|
}
|
|
boolean result = mBluetoothGatt != null;
|
|
if (result) {
|
|
setDeviceConnectionState(State.CONNECTING);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private void setDeviceConnectionState(State newState) {
|
|
LOG.debug("new device connection state: " + newState);
|
|
|
|
mGbDevice.setState(newState);
|
|
mGbDevice.sendDeviceUpdateIntent(mContext, GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
|
|
}
|
|
|
|
public void disconnect() {
|
|
synchronized (mGattMonitor) {
|
|
LOG.debug("disconnect()");
|
|
BluetoothGatt gatt = mBluetoothGatt;
|
|
if (gatt != null) {
|
|
mBluetoothGatt = null;
|
|
LOG.info("Disconnecting BtLEQueue from GATT device");
|
|
gatt.disconnect();
|
|
gatt.close();
|
|
setDeviceConnectionState(State.NOT_CONNECTED);
|
|
}
|
|
mPauseTransaction = false;
|
|
BluetoothGattServer gattServer = mBluetoothGattServer;
|
|
if (gattServer != null) {
|
|
mBluetoothGattServer = null;
|
|
gattServer.clearServices();
|
|
gattServer.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void handleDisconnected(int status) {
|
|
LOG.debug("handleDisconnected: " + status);
|
|
internalGattCallback.reset();
|
|
mTransactions.clear();
|
|
mPauseTransaction = false;
|
|
mAbortTransaction = true;
|
|
mAbortServerTransaction = true;
|
|
if (mWaitForActionResultLatch != null) {
|
|
mWaitForActionResultLatch.countDown();
|
|
}
|
|
if (mWaitForServerActionResultLatch != null) {
|
|
mWaitForServerActionResultLatch.countDown();
|
|
}
|
|
|
|
setDeviceConnectionState(State.NOT_CONNECTED);
|
|
|
|
// either we've been disconnected because the device is out of range
|
|
// or because of an explicit @{link #disconnect())
|
|
// To support automatic reconnection, we keep the mBluetoothGatt instance
|
|
// alive (we do not close() it). Unfortunately we sometimes have problems
|
|
// reconnecting automatically, so we try to fix this by re-creating mBluetoothGatt.
|
|
// Not sure if this actually works without re-initializing the device...
|
|
if (mBluetoothGatt != null) {
|
|
if (!maybeReconnect()) {
|
|
disconnect(); // ensure that we start over cleanly next time
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Depending on certain criteria, connects to the BluetoothGatt.
|
|
*
|
|
* @return true if a reconnection attempt was made, or false otherwise
|
|
*/
|
|
private boolean maybeReconnect() {
|
|
if (mAutoReconnect && mBluetoothGatt != null) {
|
|
LOG.info("Enabling automatic ble reconnect...");
|
|
boolean result = mBluetoothGatt.connect();
|
|
mPauseTransaction = false;
|
|
if (result) {
|
|
setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
|
|
}
|
|
return result;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void setPaused(boolean paused) {
|
|
mPauseTransaction = paused;
|
|
}
|
|
|
|
public void dispose() {
|
|
if (mDisposed) {
|
|
return;
|
|
}
|
|
mDisposed = true;
|
|
// try {
|
|
disconnect();
|
|
dispatchThread.interrupt();
|
|
dispatchThread = null;
|
|
// dispatchThread.join();
|
|
// } catch (InterruptedException ex) {
|
|
// LOG.error("Exception while disposing BtLEQueue", ex);
|
|
// }
|
|
}
|
|
|
|
/**
|
|
* Adds a transaction to the end of the queue.
|
|
*
|
|
* @param transaction
|
|
*/
|
|
public void add(Transaction transaction) {
|
|
LOG.debug("about to add: " + transaction);
|
|
if (!transaction.isEmpty()) {
|
|
mTransactions.add(transaction);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Aborts the currently running transaction
|
|
*/
|
|
public void abortCurrentTransaction() {
|
|
mAbortTransaction = true;
|
|
if (mWaitForActionResultLatch != null) {
|
|
mWaitForActionResultLatch.countDown();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a serverTransaction to the end of the queue
|
|
*
|
|
* @param transaction
|
|
*/
|
|
public void add(ServerTransaction transaction) {
|
|
LOG.debug("about to add: " + transaction);
|
|
if(!transaction.isEmpty()) {
|
|
mTransactions.add(transaction);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a transaction to the beginning of the queue.
|
|
* Note that actions of the *currently executing* transaction
|
|
* will still be executed before the given transaction.
|
|
*
|
|
* @param transaction
|
|
*/
|
|
public void insert(Transaction transaction) {
|
|
LOG.debug("about to insert: " + transaction);
|
|
if (!transaction.isEmpty()) {
|
|
List<AbstractTransaction> tail = new ArrayList<>(mTransactions.size() + 2);
|
|
//mTransactions.drainTo(tail);
|
|
tail.addAll(mTransactions);
|
|
mTransactions.clear();
|
|
mTransactions.add(transaction);
|
|
mTransactions.addAll(tail);
|
|
}
|
|
}
|
|
|
|
public void clear() {
|
|
mTransactions.clear();
|
|
}
|
|
|
|
/**
|
|
* Retrieves a list of supported GATT services on the connected device. This should be
|
|
* invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
|
|
*
|
|
* @return A {@code List} of supported services.
|
|
*/
|
|
public List<BluetoothGattService> getSupportedGattServices() {
|
|
if (mBluetoothGatt == null) {
|
|
LOG.warn("BluetoothGatt is null => no services available.");
|
|
return Collections.emptyList();
|
|
}
|
|
return mBluetoothGatt.getServices();
|
|
}
|
|
|
|
private boolean checkCorrectGattInstance(BluetoothGatt gatt, String where) {
|
|
if (gatt != mBluetoothGatt && mBluetoothGatt != null) {
|
|
LOG.info("Ignoring event from wrong BluetoothGatt instance: " + where + "; " + gatt);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private boolean checkCorrectBluetoothDevice(BluetoothDevice device) {
|
|
//BluetoothDevice clientDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
|
|
|
|
if(!device.getAddress().equals(mGbDevice.getAddress())) { // != clientDevice && clientDevice != null) {
|
|
LOG.info("Ignoring request from wrong Bluetooth device: " + device.getAddress());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Implements callback methods for GATT events that the app cares about. For example,
|
|
// connection change and services discovered.
|
|
private final class InternalGattCallback extends BluetoothGattCallback {
|
|
private
|
|
@Nullable
|
|
GattCallback mTransactionGattCallback;
|
|
private final GattCallback mExternalGattCallback;
|
|
|
|
public InternalGattCallback(GattCallback externalGattCallback) {
|
|
mExternalGattCallback = externalGattCallback;
|
|
}
|
|
|
|
public void setTransactionGattCallback(@Nullable GattCallback callback) {
|
|
mTransactionGattCallback = callback;
|
|
}
|
|
|
|
private GattCallback getCallbackToUse() {
|
|
if (mTransactionGattCallback != null) {
|
|
return mTransactionGattCallback;
|
|
}
|
|
return mExternalGattCallback;
|
|
}
|
|
|
|
@Override
|
|
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
|
LOG.debug("connection state change, newState: " + newState + getStatusString(status));
|
|
|
|
synchronized (mGattMonitor) {
|
|
if (mBluetoothGatt == null) {
|
|
mBluetoothGatt = gatt;
|
|
}
|
|
}
|
|
|
|
if (!checkCorrectGattInstance(gatt, "connection state event")) {
|
|
return;
|
|
}
|
|
|
|
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
LOG.warn("connection state event with error status " + status);
|
|
}
|
|
|
|
switch (newState) {
|
|
case BluetoothProfile.STATE_CONNECTED:
|
|
LOG.info("Connected to GATT server.");
|
|
setDeviceConnectionState(State.CONNECTED);
|
|
// Attempts to discover services after successful connection.
|
|
List<BluetoothGattService> cachedServices = gatt.getServices();
|
|
if (cachedServices != null && cachedServices.size() > 0) {
|
|
LOG.info("Using cached services, skipping discovery");
|
|
onServicesDiscovered(gatt, BluetoothGatt.GATT_SUCCESS);
|
|
} else {
|
|
LOG.info("Attempting to start service discovery");
|
|
// discover services in the main thread (appears to fix Samsung connection problems)
|
|
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (mBluetoothGatt != null) {
|
|
mBluetoothGatt.discoverServices();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case BluetoothProfile.STATE_DISCONNECTED:
|
|
LOG.info("Disconnected from GATT server.");
|
|
handleDisconnected(status);
|
|
break;
|
|
case BluetoothProfile.STATE_CONNECTING:
|
|
LOG.info("Connecting to GATT server...");
|
|
setDeviceConnectionState(State.CONNECTING);
|
|
break;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
|
if (!checkCorrectGattInstance(gatt, "services discovered: " + getStatusString(status))) {
|
|
return;
|
|
}
|
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
if (getCallbackToUse() != null) {
|
|
// only propagate the successful event
|
|
getCallbackToUse().onServicesDiscovered(gatt);
|
|
}
|
|
if (mConnectionLatch != null) {
|
|
mConnectionLatch.countDown();
|
|
}
|
|
} else {
|
|
LOG.warn("onServicesDiscovered received: " + status);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
|
LOG.debug("characteristic write: " + characteristic.getUuid() + getStatusString(status));
|
|
if (!checkCorrectGattInstance(gatt, "characteristic write")) {
|
|
return;
|
|
}
|
|
if (getCallbackToUse() != null) {
|
|
getCallbackToUse().onCharacteristicWrite(gatt, characteristic, status);
|
|
}
|
|
checkWaitingCharacteristic(characteristic, status);
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
|
super.onMtuChanged(gatt, mtu, status);
|
|
|
|
if(getCallbackToUse() != null){
|
|
getCallbackToUse().onMtuChanged(gatt, mtu, status);
|
|
}
|
|
|
|
mWaitForActionResultLatch.countDown();
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
public void onCharacteristicRead(BluetoothGatt gatt,
|
|
BluetoothGattCharacteristic characteristic,
|
|
int status) {
|
|
LOG.debug("characteristic read: " + characteristic.getUuid() + getStatusString(status));
|
|
if (!checkCorrectGattInstance(gatt, "characteristic read")) {
|
|
return;
|
|
}
|
|
if (getCallbackToUse() != null) {
|
|
try {
|
|
getCallbackToUse().onCharacteristicRead(gatt, characteristic, status);
|
|
} catch (Throwable ex) {
|
|
LOG.error("onCharacteristicRead: " + ex.getMessage(), ex);
|
|
}
|
|
}
|
|
checkWaitingCharacteristic(characteristic, status);
|
|
}
|
|
|
|
@Override
|
|
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
|
LOG.debug("descriptor read: " + descriptor.getUuid() + getStatusString(status));
|
|
if (!checkCorrectGattInstance(gatt, "descriptor read")) {
|
|
return;
|
|
}
|
|
if (getCallbackToUse() != null) {
|
|
try {
|
|
getCallbackToUse().onDescriptorRead(gatt, descriptor, status);
|
|
} catch (Throwable ex) {
|
|
LOG.error("onDescriptorRead: " + ex.getMessage(), ex);
|
|
}
|
|
}
|
|
checkWaitingCharacteristic(descriptor.getCharacteristic(), status);
|
|
}
|
|
|
|
@Override
|
|
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
|
LOG.debug("descriptor write: " + descriptor.getUuid() + getStatusString(status));
|
|
if (!checkCorrectGattInstance(gatt, "descriptor write")) {
|
|
return;
|
|
}
|
|
if (getCallbackToUse() != null) {
|
|
try {
|
|
getCallbackToUse().onDescriptorWrite(gatt, descriptor, status);
|
|
} catch (Throwable ex) {
|
|
LOG.error("onDescriptorWrite: " + ex.getMessage(), ex);
|
|
}
|
|
}
|
|
checkWaitingCharacteristic(descriptor.getCharacteristic(), status);
|
|
}
|
|
|
|
@Override
|
|
public void onCharacteristicChanged(BluetoothGatt gatt,
|
|
BluetoothGattCharacteristic characteristic) {
|
|
if (LOG.isDebugEnabled()) {
|
|
String content = Logging.formatBytes(characteristic.getValue());
|
|
LOG.debug("characteristic changed: " + characteristic.getUuid() + " value: " + content);
|
|
}
|
|
if (!checkCorrectGattInstance(gatt, "characteristic changed")) {
|
|
return;
|
|
}
|
|
if (getCallbackToUse() != null) {
|
|
try {
|
|
getCallbackToUse().onCharacteristicChanged(gatt, characteristic);
|
|
} catch (Throwable ex) {
|
|
LOG.error("onCharaceristicChanged: " + ex.getMessage(), ex);
|
|
}
|
|
} else {
|
|
LOG.info("No gattcallback registered, ignoring characteristic change");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
|
LOG.debug("remote rssi: " + rssi + getStatusString(status));
|
|
if (!checkCorrectGattInstance(gatt, "remote rssi")) {
|
|
return;
|
|
}
|
|
if (getCallbackToUse() != null) {
|
|
try {
|
|
getCallbackToUse().onReadRemoteRssi(gatt, rssi, status);
|
|
} catch (Throwable ex) {
|
|
LOG.error("onReadRemoteRssi: " + ex.getMessage(), ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkWaitingCharacteristic(BluetoothGattCharacteristic characteristic, int status) {
|
|
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
if (characteristic != null) {
|
|
LOG.debug("failed btle action, aborting transaction: " + characteristic.getUuid() + getStatusString(status));
|
|
}
|
|
mAbortTransaction = true;
|
|
}
|
|
if (characteristic != null && BtLEQueue.this.mWaitCharacteristic != null && characteristic.getUuid().equals(BtLEQueue.this.mWaitCharacteristic.getUuid())) {
|
|
if (mWaitForActionResultLatch != null) {
|
|
mWaitForActionResultLatch.countDown();
|
|
}
|
|
} else {
|
|
if (BtLEQueue.this.mWaitCharacteristic != null) {
|
|
LOG.error("checkWaitingCharacteristic: mismatched characteristic received: " + ((characteristic != null && characteristic.getUuid() != null) ? characteristic.getUuid().toString() : "(null)"));
|
|
}
|
|
}
|
|
}
|
|
|
|
private String getStatusString(int status) {
|
|
return status == BluetoothGatt.GATT_SUCCESS ? " (success)" : " (failed: " + status + ")";
|
|
}
|
|
|
|
public void reset() {
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("internal gatt callback set to null");
|
|
}
|
|
mTransactionGattCallback = null;
|
|
}
|
|
}
|
|
|
|
// Implements callback methods for GATT server events that the app cares about. For example,
|
|
// connection change and read/write requests.
|
|
private final class InternalGattServerCallback extends BluetoothGattServerCallback {
|
|
private
|
|
@Nullable
|
|
GattServerCallback mTransactionGattCallback;
|
|
private final GattServerCallback mExternalGattServerCallback;
|
|
|
|
public InternalGattServerCallback(GattServerCallback externalGattServerCallback) {
|
|
mExternalGattServerCallback = externalGattServerCallback;
|
|
}
|
|
|
|
public void setTransactionGattCallback(@Nullable GattServerCallback callback) {
|
|
mTransactionGattCallback = callback;
|
|
}
|
|
|
|
private GattServerCallback getCallbackToUse() {
|
|
if (mTransactionGattCallback != null) {
|
|
return mTransactionGattCallback;
|
|
}
|
|
return mExternalGattServerCallback;
|
|
}
|
|
|
|
@Override
|
|
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
|
|
LOG.debug("gatt server connection state change, newState: " + newState + getStatusString(status));
|
|
|
|
if(!checkCorrectBluetoothDevice(device)) {
|
|
return;
|
|
}
|
|
|
|
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
LOG.warn("connection state event with error status " + status);
|
|
}
|
|
}
|
|
|
|
private String getStatusString(int status) {
|
|
return status == BluetoothGatt.GATT_SUCCESS ? " (success)" : " (failed: " + status + ")";
|
|
}
|
|
|
|
@Override
|
|
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
|
|
if(!checkCorrectBluetoothDevice(device)) {
|
|
return;
|
|
}
|
|
LOG.debug("characterstic read request: " + device.getAddress() + " characteristic: " + characteristic.getUuid());
|
|
if (getCallbackToUse() != null) {
|
|
getCallbackToUse().onCharacteristicReadRequest(device, requestId, offset, characteristic);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
|
|
if(!checkCorrectBluetoothDevice(device)) {
|
|
return;
|
|
}
|
|
LOG.debug("characteristic write request: " + device.getAddress() + " characteristic: " + characteristic.getUuid());
|
|
boolean success = false;
|
|
if (getCallbackToUse() != null) {
|
|
success = getCallbackToUse().onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
|
|
}
|
|
if (responseNeeded && mSendWriteRequestResponse) {
|
|
mBluetoothGattServer.sendResponse(device, requestId, success ? BluetoothGatt.GATT_SUCCESS : BluetoothGatt.GATT_FAILURE, 0, new byte[0]);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
|
|
if(!checkCorrectBluetoothDevice(device)) {
|
|
return;
|
|
}
|
|
LOG.debug("onDescriptorReadRequest: " + device.getAddress());
|
|
if(getCallbackToUse() != null) {
|
|
getCallbackToUse().onDescriptorReadRequest(device, requestId, offset, descriptor);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
|
|
if(!checkCorrectBluetoothDevice(device)) {
|
|
return;
|
|
}
|
|
LOG.debug("onDescriptorWriteRequest: " + device.getAddress());
|
|
boolean success = false;
|
|
if(getCallbackToUse() != null) {
|
|
success = getCallbackToUse().onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
|
|
}
|
|
if (responseNeeded && mSendWriteRequestResponse) {
|
|
mBluetoothGattServer.sendResponse(device, requestId, success ? BluetoothGatt.GATT_SUCCESS : BluetoothGatt.GATT_FAILURE, 0, new byte[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|