2015-04-19 02:37:29 +02:00
|
|
|
package nodomain.freeyourgadget.gadgetbridge.btle;
|
|
|
|
|
|
|
|
import android.bluetooth.BluetoothAdapter;
|
|
|
|
import android.bluetooth.BluetoothDevice;
|
|
|
|
import android.bluetooth.BluetoothGatt;
|
|
|
|
import android.bluetooth.BluetoothGattCallback;
|
|
|
|
import android.bluetooth.BluetoothGattCharacteristic;
|
|
|
|
import android.bluetooth.BluetoothGattService;
|
|
|
|
import android.bluetooth.BluetoothProfile;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.util.Log;
|
|
|
|
|
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;
|
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
/**
|
|
|
|
* One queue/thread per connectable device.
|
|
|
|
*/
|
|
|
|
public final class BtLEQueue {
|
|
|
|
private static final String TAG = BtLEQueue.class.getSimpleName();
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
private GBDevice mGbDevice;
|
|
|
|
private BluetoothAdapter mBluetoothAdapter;
|
|
|
|
private BluetoothGatt mBluetoothGatt;
|
|
|
|
private volatile BlockingQueue<Transaction> mTransactions = new LinkedBlockingQueue<Transaction>();
|
|
|
|
private volatile boolean mDisposed;
|
|
|
|
private volatile boolean mAbortTransaction;
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +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
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
private Thread dispatchThread = new Thread("Bluetooth GATT Dispatcher") {
|
|
|
|
|
|
|
|
public void run() {
|
2015-04-19 11:28:03 +02:00
|
|
|
while (!mDisposed) {
|
|
|
|
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();
|
|
|
|
if (action.run(mBluetoothGatt)) {
|
|
|
|
mWaitForActionResultLatch = new CountDownLatch(1);
|
|
|
|
mWaitForActionResultLatch.await();
|
|
|
|
mWaitForActionResultLatch = null;
|
|
|
|
if (mAbortTransaction) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Log.e(TAG, "Action returned false: " + action);
|
|
|
|
break; // abort the transaction
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (InterruptedException ignored) {
|
|
|
|
mWaitForActionResultLatch = null;
|
|
|
|
mConnectionLatch = null;
|
|
|
|
} finally {
|
|
|
|
mWaitCharacteristic = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Log.i(TAG, "Queue Dispatch Thread terminated.");
|
|
|
|
}
|
2015-04-19 02:37:29 +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
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
dispatchThread.start();
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +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-19 02:37:29 +02:00
|
|
|
* @return true whether the connection attempt was successfully triggered
|
|
|
|
*/
|
|
|
|
public boolean connect() {
|
|
|
|
if (mBluetoothGatt != null) {
|
|
|
|
disconnect();
|
|
|
|
}
|
|
|
|
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
|
|
|
|
mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback);
|
|
|
|
boolean result = mBluetoothGatt.connect();
|
|
|
|
setDeviceConnectionState(result ? State.CONNECTING : State.NOT_CONNECTED);
|
|
|
|
return result;
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
private void setDeviceConnectionState(State newState) {
|
|
|
|
mGbDevice.setState(newState);
|
|
|
|
mGbDevice.sendDeviceUpdateIntent(mContext);
|
|
|
|
if (mConnectionLatch != null) {
|
|
|
|
mConnectionLatch.countDown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void disconnect() {
|
|
|
|
if (mBluetoothGatt != null) {
|
|
|
|
Log.i(TAG, "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);
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
public void dispose() {
|
|
|
|
if (mDisposed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mDisposed = true;
|
|
|
|
// try {
|
2015-04-19 11:28:03 +02:00
|
|
|
disconnect();
|
|
|
|
dispatchThread.interrupt();
|
|
|
|
dispatchThread = null;
|
2015-04-19 02:37:29 +02:00
|
|
|
// dispatchThread.join();
|
|
|
|
// } catch (InterruptedException ex) {
|
|
|
|
// Log.e(TAG, "Exception while disposing BtLEQueue", ex);
|
|
|
|
// }
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
/**
|
|
|
|
* Adds a transaction to the end of the queue.
|
2015-04-19 11:28:03 +02:00
|
|
|
*
|
2015-04-19 02:37:29 +02:00
|
|
|
* @param transaction
|
|
|
|
*/
|
|
|
|
public void add(Transaction transaction) {
|
|
|
|
if (!transaction.isEmpty()) {
|
|
|
|
mTransactions.add(transaction);
|
|
|
|
}
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
public void clear() {
|
|
|
|
mTransactions.clear();
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +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) {
|
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
return mBluetoothGatt.getServices();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
switch (newState) {
|
2015-04-19 11:28:03 +02:00
|
|
|
case BluetoothProfile.STATE_CONNECTED:
|
|
|
|
Log.i(TAG, "Connected to GATT server.");
|
|
|
|
setDeviceConnectionState(State.CONNECTED);
|
|
|
|
// Attempts to discover services after successful connection.
|
|
|
|
Log.i(TAG, "Attempting to start service discovery:" +
|
|
|
|
mBluetoothGatt.discoverServices());
|
|
|
|
break;
|
|
|
|
case BluetoothProfile.STATE_DISCONNECTED:
|
|
|
|
Log.i(TAG, "Disconnected from GATT server.");
|
|
|
|
handleDisconnected();
|
|
|
|
break;
|
|
|
|
case BluetoothProfile.STATE_CONNECTING:
|
|
|
|
Log.i(TAG, "Connecting to GATT server...");
|
|
|
|
setDeviceConnectionState(State.CONNECTING);
|
|
|
|
break;
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
|
|
if (mExternalGattCallback != null) {
|
|
|
|
// only propagate the successful event
|
|
|
|
mExternalGattCallback.onServicesDiscovered(gatt);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Log.w(TAG, "onServicesDiscovered received: " + status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
|
|
Log.e(TAG, "Writing characteristic " + characteristic.getUuid() + " succeeded.");
|
|
|
|
} else {
|
2015-04-19 11:28:03 +02:00
|
|
|
Log.e(TAG, "Writing characteristic " + characteristic.getUuid() + " failed: " + status);
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
if (mExternalGattCallback != null) {
|
|
|
|
mExternalGattCallback.onCharacteristicWrite(gatt, characteristic, status);
|
|
|
|
}
|
|
|
|
checkWaitingCharacteristic(characteristic, status);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCharacteristicRead(BluetoothGatt gatt,
|
|
|
|
BluetoothGattCharacteristic characteristic,
|
|
|
|
int status) {
|
|
|
|
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
|
|
Log.e(TAG, "Reading characteristic " + characteristic.getUuid() + " failed: " + status);
|
|
|
|
}
|
|
|
|
if (mExternalGattCallback != null) {
|
|
|
|
mExternalGattCallback.onCharacteristicRead(gatt, characteristic, status);
|
|
|
|
}
|
|
|
|
checkWaitingCharacteristic(characteristic, status);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCharacteristicChanged(BluetoothGatt gatt,
|
|
|
|
BluetoothGattCharacteristic characteristic) {
|
|
|
|
if (mExternalGattCallback != null) {
|
|
|
|
mExternalGattCallback.onCharacteristicChanged(gatt, characteristic);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
|
|
|
if (mExternalGattCallback != null) {
|
|
|
|
mExternalGattCallback.onReadRemoteRssi(gatt, rssi, status);
|
|
|
|
}
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
private void checkWaitingCharacteristic(BluetoothGattCharacteristic characteristic, int status) {
|
|
|
|
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
|
|
mAbortTransaction = true;
|
|
|
|
}
|
|
|
|
if (characteristic != null && BtLEQueue.this.mWaitCharacteristic != null && characteristic.getUuid().equals(BtLEQueue.this.mWaitCharacteristic.getUuid())) {
|
|
|
|
if (mWaitForActionResultLatch != null) {
|
|
|
|
mWaitForActionResultLatch.countDown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|