1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-07-10 15:34:50 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/btle/BtLEQueue.java

385 lines
16 KiB
Java
Raw Normal View History

package nodomain.freeyourgadget.gadgetbridge.btle;
2015-04-20 23:45:34 +02:00
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
2015-04-20 23:45:34 +02:00
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
2015-04-19 11:28:03 +02:00
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import nodomain.freeyourgadget.gadgetbridge.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.GBDevice.State;
/**
* One queue/thread per connectable device.
*/
public final class BtLEQueue {
private static final Logger LOG = LoggerFactory.getLogger(BtLEQueue.class);
2015-04-19 11:28:03 +02:00
private Object mGattMonitor = new Object();
private GBDevice mGbDevice;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothGatt mBluetoothGatt;
private volatile BlockingQueue<Transaction> mTransactions = new LinkedBlockingQueue<Transaction>();
private volatile boolean mDisposed;
2015-04-25 23:47:28 +02:00
private volatile boolean mCrashed;
private volatile boolean mAbortTransaction;
2015-04-19 11:28:03 +02:00
private Context mContext;
private CountDownLatch mWaitForActionResultLatch;
private CountDownLatch mConnectionLatch;
private BluetoothGattCharacteristic mWaitCharacteristic;
private GattCallback mExternalGattCallback;
2015-04-19 11:28:03 +02:00
private Thread dispatchThread = new Thread("GadgetBridge GATT Dispatcher") {
2015-04-20 23:45:34 +02:00
@Override
public void run() {
LOG.debug("Queue Dispatch Thread started.");
2015-04-25 23:47:28 +02:00
while (!mDisposed && !mCrashed) {
2015-04-19 11:28:03 +02:00
try {
Transaction transaction = mTransactions.take();
if (!isConnected()) {
// TODO: request connection and initialization from the outside and wait until finished
// 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;
}
mAbortTransaction = false;
// Run all actions of the transaction until one doesn't succeed
for (BtLEAction action : transaction.getActions()) {
mWaitCharacteristic = action.getCharacteristic();
mWaitForActionResultLatch = new CountDownLatch(1);
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;
}
2015-04-19 11:28:03 +02:00
}
} else {
LOG.error("Action returned false: " + action);
2015-04-19 11:28:03 +02:00
break; // abort the transaction
}
}
} catch (InterruptedException ignored) {
mConnectionLatch = null;
LOG.debug("Thread interrupted");
2015-04-25 23:47:28 +02:00
} catch (Throwable ex) {
LOG.error("Queue Dispatch Thread died: " + ex.getMessage());
2015-04-25 23:47:28 +02:00
mCrashed = true;
mConnectionLatch = null;
2015-04-19 11:28:03 +02:00
} finally {
mWaitForActionResultLatch = null;
2015-04-19 11:28:03 +02:00
mWaitCharacteristic = null;
}
}
LOG.info("Queue Dispatch Thread terminated.");
2015-04-19 11:28:03 +02:00
}
};
public BtLEQueue(BluetoothAdapter bluetoothAdapter, GBDevice gbDevice, GattCallback externalGattCallback, Context context) {
mBluetoothAdapter = bluetoothAdapter;
mGbDevice = gbDevice;
mExternalGattCallback = externalGattCallback;
mContext = context;
2015-04-19 11:28:03 +02:00
dispatchThread.start();
}
2015-04-19 11:28:03 +02:00
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.
2015-04-19 11:28:03 +02:00
*
2015-04-25 23:47:28 +02:00
* @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() {
2015-04-25 23:47:28 +02:00
if (isConnected()) {
LOG.warn("Ingoring connect() because already connected.");
2015-04-25 23:47:28 +02:00
return false;
}
synchronized (mGattMonitor) {
if (mBluetoothGatt != null) {
// Tribal knowledge says you're better off not reusing existing BlueoothGatt connections,
// so create a new one.
LOG.info("connect() requested -- disconnecting previous connection: " + mGbDevice.getName());
disconnect();
}
}
LOG.info("Attempting to connect to " + mGbDevice.getName());
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
boolean result = false;
synchronized (mGattMonitor) {
mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback);
result = mBluetoothGatt.connect();
}
setDeviceConnectionState(result ? State.CONNECTING : State.NOT_CONNECTED);
return result;
}
2015-04-19 11:28:03 +02:00
private void setDeviceConnectionState(State newState) {
mGbDevice.setState(newState);
mGbDevice.sendDeviceUpdateIntent(mContext);
if (mConnectionLatch != null) {
mConnectionLatch.countDown();
}
}
public void disconnect() {
synchronized (mGattMonitor) {
if (mBluetoothGatt != null) {
LOG.info("Disconnecting BtLEQueue from GATT device");
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
mBluetoothGatt = null;
}
}
}
private void handleDisconnected() {
mTransactions.clear();
if (mWaitForActionResultLatch != null) {
mWaitForActionResultLatch.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).
}
2015-04-19 11:28:03 +02:00
public void dispose() {
if (mDisposed) {
return;
}
mDisposed = true;
// try {
2015-04-19 11:28:03 +02:00
disconnect();
dispatchThread.interrupt();
dispatchThread = null;
// dispatchThread.join();
// } catch (InterruptedException ex) {
// LOG.error("Exception while disposing BtLEQueue", ex);
// }
}
2015-04-19 11:28:03 +02:00
/**
* Adds a transaction to the end of the queue.
2015-04-19 11:28:03 +02:00
*
* @param transaction
*/
public void add(Transaction transaction) {
LOG.debug("about to add: " + transaction);
if (!transaction.isEmpty()) {
mTransactions.add(transaction);
}
}
2015-04-19 11:28:03 +02:00
public void clear() {
mTransactions.clear();
}
2015-04-19 11:28:03 +02:00
/**
* 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) {
LOG.info("Ignoring event from wrong BluetoothGatt instance: " + where);
return false;
}
return true;
}
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private final BluetoothGattCallback internalGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
2015-05-25 23:14:02 +02:00
LOG.debug("connection state change, newState: " + newState + getStatusString(status));
if (!checkCorrectGattInstance(gatt, "connection state event")) {
return;
}
if (status != BluetoothGatt.GATT_SUCCESS) {
LOG.warn("connection state event with error status " + status);
}
switch (newState) {
2015-04-19 11:28:03 +02:00
case BluetoothProfile.STATE_CONNECTED:
LOG.info("Connected to GATT server.");
2015-04-19 11:28:03 +02:00
setDeviceConnectionState(State.CONNECTED);
// Attempts to discover services after successful connection.
LOG.info("Attempting to start service discovery:" +
2015-04-19 11:28:03 +02:00
mBluetoothGatt.discoverServices());
break;
case BluetoothProfile.STATE_DISCONNECTED:
LOG.info("Disconnected from GATT server.");
2015-04-19 11:28:03 +02:00
handleDisconnected();
break;
case BluetoothProfile.STATE_CONNECTING:
LOG.info("Connecting to GATT server...");
2015-04-19 11:28:03 +02:00
setDeviceConnectionState(State.CONNECTING);
break;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
2015-05-25 23:14:02 +02:00
if (!checkCorrectGattInstance(gatt, "services discovered: " + getStatusString(status))) {
return;
}
if (status == BluetoothGatt.GATT_SUCCESS) {
if (mExternalGattCallback != null) {
// only propagate the successful event
mExternalGattCallback.onServicesDiscovered(gatt);
}
} else {
LOG.warn("onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
2015-05-25 23:14:02 +02:00
LOG.debug("characteristic write: " + characteristic.getUuid() + getStatusString(status));
if (!checkCorrectGattInstance(gatt, "characteristic write")) {
return;
}
if (mExternalGattCallback != null) {
mExternalGattCallback.onCharacteristicWrite(gatt, characteristic, status);
}
checkWaitingCharacteristic(characteristic, status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
2015-05-25 23:14:02 +02:00
LOG.debug("characteristic read: " + characteristic.getUuid() + getStatusString(status));
if (!checkCorrectGattInstance(gatt, "characteristic read")) {
return;
}
if (mExternalGattCallback != null) {
mExternalGattCallback.onCharacteristicRead(gatt, characteristic, status);
}
checkWaitingCharacteristic(characteristic, status);
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
2015-05-25 23:14:02 +02:00
LOG.debug("descriptor read: " + descriptor.getUuid() + getStatusString(status));
if (!checkCorrectGattInstance(gatt, "descriptor read")) {
return;
}
if (mExternalGattCallback != null) {
mExternalGattCallback.onDescriptorRead(gatt, descriptor, status);
}
checkWaitingCharacteristic(descriptor.getCharacteristic(), status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
2015-05-25 23:14:02 +02:00
LOG.debug("descriptor write: " + descriptor.getUuid() + getStatusString(status));
if (!checkCorrectGattInstance(gatt, "descriptor write")) {
return;
}
if (mExternalGattCallback != null) {
mExternalGattCallback.onDescriptorWrite(gatt, descriptor, status);
}
checkWaitingCharacteristic(descriptor.getCharacteristic(), status);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
LOG.debug("characteristic changed: " + characteristic.getUuid());
if (!checkCorrectGattInstance(gatt, "characteristic changed")) {
return;
}
if (gatt != mBluetoothGatt) {
LOG.info("Ignoring characteristic change event from wrong BluetoothGatt instance");
return;
}
if (mExternalGattCallback != null) {
mExternalGattCallback.onCharacteristicChanged(gatt, characteristic);
}
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
2015-05-25 23:14:02 +02:00
LOG.debug("remote rssi: " + rssi + getStatusString(status));
if (!checkCorrectGattInstance(gatt, "remote rssi")) {
return;
}
if (gatt != mBluetoothGatt) {
LOG.info("Ignoring remote rssi event from wrong BluetoothGatt instance");
return;
}
if (mExternalGattCallback != null) {
mExternalGattCallback.onReadRemoteRssi(gatt, rssi, status);
}
}
2015-04-19 11:28:03 +02:00
private void checkWaitingCharacteristic(BluetoothGattCharacteristic characteristic, int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
2015-05-25 23:14:02 +02:00
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().toString() : "(null)");
}
}
}
};
2015-05-25 23:14:02 +02:00
private String getStatusString(int status) {
return status == BluetoothGatt.GATT_SUCCESS ? " (success)" : " (failed: " + status + ")";
}
}