2018-02-26 14:27:32 +01:00
|
|
|
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
2017-03-10 14:53:19 +01:00
|
|
|
Gobbetti, Sergey Trofimov, 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/>. */
|
2015-08-03 23:09:49 +02:00
|
|
|
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
2015-04-19 02:37:29 +02:00
|
|
|
|
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;
|
2015-05-24 14:39:36 +02:00
|
|
|
import android.bluetooth.BluetoothGattDescriptor;
|
2015-04-20 23:45:34 +02:00
|
|
|
import android.bluetooth.BluetoothGattService;
|
|
|
|
import android.bluetooth.BluetoothProfile;
|
|
|
|
import android.content.Context;
|
2018-09-06 18:21:21 +02:00
|
|
|
import android.os.Handler;
|
|
|
|
import android.os.Looper;
|
2015-08-05 23:24:58 +02:00
|
|
|
import android.support.annotation.Nullable;
|
2015-05-12 06:28:11 +02:00
|
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2016-09-20 23:09:42 +02:00
|
|
|
import java.util.ArrayList;
|
2015-04-26 00:53:48 +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;
|
|
|
|
|
2018-09-06 18:21:21 +02:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
2017-10-19 21:52:38 +02:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
2015-08-03 23:09:49 +02:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
2015-08-05 23:24:58 +02:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
2015-04-26 00:53:48 +02:00
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
/**
|
|
|
|
* One queue/thread per connectable device.
|
|
|
|
*/
|
|
|
|
public final class BtLEQueue {
|
2015-05-12 06:28:11 +02:00
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(BtLEQueue.class);
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-11-23 22:46:12 +01:00
|
|
|
private final Object mGattMonitor = new Object();
|
2015-11-23 23:04:46 +01:00
|
|
|
private final GBDevice mGbDevice;
|
|
|
|
private final BluetoothAdapter mBluetoothAdapter;
|
2015-04-19 02:37:29 +02:00
|
|
|
private BluetoothGatt mBluetoothGatt;
|
2015-07-13 21:54:46 +02:00
|
|
|
|
2015-11-23 23:04:46 +01:00
|
|
|
private final BlockingQueue<Transaction> mTransactions = new LinkedBlockingQueue<>();
|
2015-04-19 02:37:29 +02:00
|
|
|
private volatile boolean mDisposed;
|
2015-04-25 23:47:28 +02:00
|
|
|
private volatile boolean mCrashed;
|
2015-04-19 02:37:29 +02:00
|
|
|
private volatile boolean mAbortTransaction;
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-11-23 23:04:46 +01:00
|
|
|
private final Context mContext;
|
2015-04-19 02:37:29 +02:00
|
|
|
private CountDownLatch mWaitForActionResultLatch;
|
|
|
|
private CountDownLatch mConnectionLatch;
|
|
|
|
private BluetoothGattCharacteristic mWaitCharacteristic;
|
2015-08-05 23:24:58 +02:00
|
|
|
private final InternalGattCallback internalGattCallback;
|
2016-04-28 23:17:13 +02:00
|
|
|
private boolean mAutoReconnect;
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2016-12-01 17:28:51 +01:00
|
|
|
private Thread dispatchThread = new Thread("Gadgetbridge GATT Dispatcher") {
|
2015-04-19 02:37:29 +02:00
|
|
|
|
2015-04-20 23:45:34 +02:00
|
|
|
@Override
|
2015-04-19 02:37:29 +02:00
|
|
|
public void run() {
|
2015-05-12 06:28:11 +02:00
|
|
|
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();
|
2015-08-05 23:24:58 +02:00
|
|
|
|
2015-04-19 11:28:03 +02:00
|
|
|
if (!isConnected()) {
|
2016-02-18 23:35:55 +01:00
|
|
|
LOG.debug("not connected, waiting for connection...");
|
2015-04-19 11:28:03 +02:00
|
|
|
// TODO: request connection and initialization from the outside and wait until finished
|
2015-09-10 00:00:52 +02:00
|
|
|
internalGattCallback.reset();
|
2015-04-19 11:28:03 +02:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2015-08-05 23:24:58 +02:00
|
|
|
internalGattCallback.setTransactionGattCallback(transaction.getGattCallback());
|
2015-04-19 11:28:03 +02:00
|
|
|
mAbortTransaction = false;
|
|
|
|
// Run all actions of the transaction until one doesn't succeed
|
|
|
|
for (BtLEAction action : transaction.getActions()) {
|
2015-08-14 00:23:01 +02:00
|
|
|
if (mAbortTransaction) { // got disconnected
|
|
|
|
LOG.info("Aborting running transaction");
|
|
|
|
break;
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
mWaitCharacteristic = action.getCharacteristic();
|
2015-05-24 14:39:36 +02:00
|
|
|
mWaitForActionResultLatch = new CountDownLatch(1);
|
2015-07-03 21:58:13 +02:00
|
|
|
if (LOG.isDebugEnabled()) {
|
|
|
|
LOG.debug("About to run action: " + action);
|
|
|
|
}
|
2018-09-14 20:11:27 +02:00
|
|
|
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());
|
|
|
|
}
|
2015-05-17 21:55:02 +02:00
|
|
|
if (action.run(mBluetoothGatt)) {
|
2015-05-24 14:39:36 +02:00
|
|
|
// check again, maybe due to some condition, action did not need to write, so we can't wait
|
|
|
|
boolean waitForResult = action.expectsResult();
|
2015-05-17 21:55:02 +02:00
|
|
|
if (waitForResult) {
|
|
|
|
mWaitForActionResultLatch.await();
|
|
|
|
mWaitForActionResultLatch = null;
|
|
|
|
if (mAbortTransaction) {
|
|
|
|
break;
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
}
|
|
|
|
} else {
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.error("Action returned false: " + action);
|
2015-04-19 11:28:03 +02:00
|
|
|
break; // abort the transaction
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (InterruptedException ignored) {
|
|
|
|
mConnectionLatch = null;
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.debug("Thread interrupted");
|
2015-04-25 23:47:28 +02:00
|
|
|
} catch (Throwable ex) {
|
2015-10-18 22:08:52 +02:00
|
|
|
LOG.error("Queue Dispatch Thread died: " + ex.getMessage(), ex);
|
2015-04-25 23:47:28 +02:00
|
|
|
mCrashed = true;
|
|
|
|
mConnectionLatch = null;
|
2015-04-19 11:28:03 +02:00
|
|
|
} finally {
|
2015-05-28 00:26:41 +02:00
|
|
|
mWaitForActionResultLatch = null;
|
2015-04-19 11:28:03 +02:00
|
|
|
mWaitCharacteristic = null;
|
|
|
|
}
|
|
|
|
}
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.info("Queue Dispatch Thread terminated.");
|
2015-04-19 11:28:03 +02:00
|
|
|
}
|
2015-04-19 02:37:29 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
public BtLEQueue(BluetoothAdapter bluetoothAdapter, GBDevice gbDevice, GattCallback externalGattCallback, Context context) {
|
|
|
|
mBluetoothAdapter = bluetoothAdapter;
|
|
|
|
mGbDevice = gbDevice;
|
2015-08-05 23:24:58 +02:00
|
|
|
internalGattCallback = new InternalGattCallback(externalGattCallback);
|
2015-04-19 02:37:29 +02:00
|
|
|
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
|
|
|
|
2016-04-28 23:17:13 +02:00
|
|
|
public void setAutoReconnect(boolean enable) {
|
|
|
|
mAutoReconnect = enable;
|
|
|
|
}
|
|
|
|
|
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-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
|
2015-04-19 02:37:29 +02:00
|
|
|
*/
|
|
|
|
public boolean connect() {
|
2015-04-25 23:47:28 +02:00
|
|
|
if (isConnected()) {
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.warn("Ingoring connect() because already connected.");
|
2015-04-25 23:47:28 +02:00
|
|
|
return false;
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
2015-05-22 23:15:45 +02:00
|
|
|
synchronized (mGattMonitor) {
|
|
|
|
if (mBluetoothGatt != null) {
|
2016-12-01 20:18:36 +01:00
|
|
|
// Tribal knowledge says you're better off not reusing existing BluetoothGatt connections,
|
2015-05-22 23:15:45 +02:00
|
|
|
// so create a new one.
|
|
|
|
LOG.info("connect() requested -- disconnecting previous connection: " + mGbDevice.getName());
|
|
|
|
disconnect();
|
|
|
|
}
|
|
|
|
}
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.info("Attempting to connect to " + mGbDevice.getName());
|
2015-11-01 23:32:25 +01:00
|
|
|
mBluetoothAdapter.cancelDiscovery();
|
2015-04-19 02:37:29 +02:00
|
|
|
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
|
2015-05-22 23:15:45 +02:00
|
|
|
synchronized (mGattMonitor) {
|
2016-04-09 09:53:03 +02:00
|
|
|
// connectGatt with true doesn't really work ;( too often connection problems
|
2018-09-06 18:21:21 +02:00
|
|
|
if (GBApplication.isRunningMarshmallowOrLater()) {
|
|
|
|
mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback, BluetoothDevice.TRANSPORT_LE);
|
|
|
|
} else {
|
|
|
|
mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback);
|
|
|
|
}
|
2015-05-22 23:15:45 +02:00
|
|
|
}
|
2016-04-09 09:53:03 +02:00
|
|
|
boolean result = mBluetoothGatt != null;
|
2015-10-22 00:32:16 +02:00
|
|
|
if (result) {
|
|
|
|
setDeviceConnectionState(State.CONNECTING);
|
|
|
|
}
|
|
|
|
return result;
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
private void setDeviceConnectionState(State newState) {
|
2016-02-18 23:35:55 +01:00
|
|
|
LOG.debug("new device connection state: " + newState);
|
2015-04-19 02:37:29 +02:00
|
|
|
mGbDevice.setState(newState);
|
|
|
|
mGbDevice.sendDeviceUpdateIntent(mContext);
|
2016-02-18 23:35:55 +01:00
|
|
|
if (mConnectionLatch != null && newState == State.CONNECTED) {
|
2015-04-19 02:37:29 +02:00
|
|
|
mConnectionLatch.countDown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void disconnect() {
|
2015-05-22 23:15:45 +02:00
|
|
|
synchronized (mGattMonitor) {
|
2016-02-18 23:35:55 +01:00
|
|
|
LOG.debug("disconnect()");
|
2015-08-14 00:23:01 +02:00
|
|
|
BluetoothGatt gatt = mBluetoothGatt;
|
|
|
|
if (gatt != null) {
|
2015-05-22 23:15:45 +02:00
|
|
|
mBluetoothGatt = null;
|
2015-08-14 00:23:01 +02:00
|
|
|
LOG.info("Disconnecting BtLEQueue from GATT device");
|
|
|
|
gatt.disconnect();
|
|
|
|
gatt.close();
|
2015-06-07 15:31:42 +02:00
|
|
|
setDeviceConnectionState(State.NOT_CONNECTED);
|
2015-05-22 23:15:45 +02:00
|
|
|
}
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-13 21:54:46 +02:00
|
|
|
private void handleDisconnected(int status) {
|
2016-02-18 23:35:55 +01:00
|
|
|
LOG.debug("handleDisconnected: " + status);
|
2015-08-18 00:08:22 +02:00
|
|
|
internalGattCallback.reset();
|
2015-04-19 02:37:29 +02:00
|
|
|
mTransactions.clear();
|
2015-08-14 00:23:01 +02:00
|
|
|
mAbortTransaction = true;
|
2015-04-19 02:37:29 +02:00
|
|
|
if (mWaitForActionResultLatch != null) {
|
|
|
|
mWaitForActionResultLatch.countDown();
|
|
|
|
}
|
2016-07-04 23:38:25 +02:00
|
|
|
boolean wasInitialized = mGbDevice.isInitialized();
|
2015-04-19 02:37:29 +02:00
|
|
|
setDeviceConnectionState(State.NOT_CONNECTED);
|
2015-07-13 21:54:46 +02:00
|
|
|
|
2015-05-22 23:15:45 +02:00
|
|
|
// 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
|
2015-07-13 21:54:46 +02:00
|
|
|
// alive (we do not close() it). Unfortunately we sometimes have problems
|
|
|
|
// reconnecting automatically, so we try to fix this by re-creating mBluetoothGatt.
|
2015-08-14 00:23:01 +02:00
|
|
|
// Not sure if this actually works without re-initializing the device...
|
2017-04-20 23:00:43 +02:00
|
|
|
if (mBluetoothGatt != null) {
|
2016-07-04 23:38:25 +02:00
|
|
|
if (!wasInitialized || !maybeReconnect()) {
|
2015-08-14 00:23:01 +02:00
|
|
|
disconnect(); // ensure that we start over cleanly next time
|
|
|
|
}
|
2015-07-13 21:54:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-14 00:23:01 +02:00
|
|
|
/**
|
|
|
|
* Depending on certain criteria, connects to the BluetoothGatt.
|
2015-09-24 14:45:21 +02:00
|
|
|
*
|
2015-08-14 00:23:01 +02:00
|
|
|
* @return true if a reconnection attempt was made, or false otherwise
|
|
|
|
*/
|
|
|
|
private boolean maybeReconnect() {
|
2016-04-28 23:17:13 +02:00
|
|
|
if (mAutoReconnect && mBluetoothGatt != null) {
|
|
|
|
LOG.info("Enabling automatic ble reconnect...");
|
2016-05-26 23:48:05 +02:00
|
|
|
boolean result = mBluetoothGatt.connect();
|
|
|
|
if (result) {
|
|
|
|
setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
|
|
|
|
}
|
|
|
|
return result;
|
2015-07-13 21:54:46 +02:00
|
|
|
}
|
2015-08-14 00:23:01 +02:00
|
|
|
return false;
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
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) {
|
2015-05-12 06:28:11 +02:00
|
|
|
// LOG.error("Exception while disposing BtLEQueue", ex);
|
2015-04-19 02:37:29 +02:00
|
|
|
// }
|
|
|
|
}
|
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) {
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.debug("about to add: " + transaction);
|
2015-04-19 02:37:29 +02:00
|
|
|
if (!transaction.isEmpty()) {
|
|
|
|
mTransactions.add(transaction);
|
|
|
|
}
|
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
|
2016-09-20 23:09:42 +02:00
|
|
|
/**
|
|
|
|
* 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<Transaction> tail = new ArrayList<>(mTransactions.size() + 2);
|
|
|
|
mTransactions.drainTo(tail);
|
|
|
|
mTransactions.add(transaction);
|
|
|
|
mTransactions.addAll(tail);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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) {
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.warn("BluetoothGatt is null => no services available.");
|
2015-04-19 02:37:29 +02:00
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
return mBluetoothGatt.getServices();
|
|
|
|
}
|
|
|
|
|
2015-05-22 23:15:45 +02:00
|
|
|
private boolean checkCorrectGattInstance(BluetoothGatt gatt, String where) {
|
2016-02-13 00:09:35 +01:00
|
|
|
if (gatt != mBluetoothGatt && mBluetoothGatt != null) {
|
|
|
|
LOG.info("Ignoring event from wrong BluetoothGatt instance: " + where + "; " + gatt);
|
2015-05-22 23:15:45 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
// Implements callback methods for GATT events that the app cares about. For example,
|
|
|
|
// connection change and services discovered.
|
2015-08-05 23:24:58 +02:00
|
|
|
private final class InternalGattCallback extends BluetoothGattCallback {
|
2015-09-24 14:45:21 +02:00
|
|
|
private
|
|
|
|
@Nullable
|
|
|
|
GattCallback mTransactionGattCallback;
|
2015-11-23 23:04:46 +01:00
|
|
|
private final GattCallback mExternalGattCallback;
|
2015-08-05 23:24:58 +02:00
|
|
|
|
|
|
|
public InternalGattCallback(GattCallback externalGattCallback) {
|
|
|
|
mExternalGattCallback = externalGattCallback;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setTransactionGattCallback(@Nullable GattCallback callback) {
|
|
|
|
mTransactionGattCallback = callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
private GattCallback getCallbackToUse() {
|
|
|
|
if (mTransactionGattCallback != null) {
|
|
|
|
return mTransactionGattCallback;
|
|
|
|
}
|
|
|
|
return mExternalGattCallback;
|
|
|
|
}
|
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
@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));
|
2015-05-24 23:12:54 +02:00
|
|
|
|
2016-05-26 19:03:38 +02:00
|
|
|
synchronized (mGattMonitor) {
|
|
|
|
if (mBluetoothGatt == null) {
|
|
|
|
mBluetoothGatt = gatt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-22 23:15:45 +02:00
|
|
|
if (!checkCorrectGattInstance(gatt, "connection state event")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-24 23:12:54 +02:00
|
|
|
if (status != BluetoothGatt.GATT_SUCCESS) {
|
2015-05-28 23:26:17 +02:00
|
|
|
LOG.warn("connection state event with error status " + status);
|
2015-05-24 23:12:54 +02:00
|
|
|
}
|
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
switch (newState) {
|
2015-04-19 11:28:03 +02:00
|
|
|
case BluetoothProfile.STATE_CONNECTED:
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.info("Connected to GATT server.");
|
2015-04-19 11:28:03 +02:00
|
|
|
setDeviceConnectionState(State.CONNECTED);
|
|
|
|
// Attempts to discover services after successful connection.
|
2016-07-08 22:15:36 +02:00
|
|
|
List<BluetoothGattService> cachedServices = gatt.getServices();
|
|
|
|
if (cachedServices != null && cachedServices.size() > 0) {
|
|
|
|
LOG.info("Using cached services, skipping discovery");
|
|
|
|
onServicesDiscovered(gatt, BluetoothGatt.GATT_SUCCESS);
|
|
|
|
} else {
|
2018-09-06 18:21:21 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2016-07-08 22:15:36 +02:00
|
|
|
}
|
2015-04-19 11:28:03 +02:00
|
|
|
break;
|
|
|
|
case BluetoothProfile.STATE_DISCONNECTED:
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.info("Disconnected from GATT server.");
|
2015-07-13 21:54:46 +02:00
|
|
|
handleDisconnected(status);
|
2015-04-19 11:28:03 +02:00
|
|
|
break;
|
|
|
|
case BluetoothProfile.STATE_CONNECTING:
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.info("Connecting to GATT server...");
|
2015-04-19 11:28:03 +02:00
|
|
|
setDeviceConnectionState(State.CONNECTING);
|
|
|
|
break;
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
2015-05-25 23:14:02 +02:00
|
|
|
if (!checkCorrectGattInstance(gatt, "services discovered: " + getStatusString(status))) {
|
2015-05-22 23:15:45 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
2015-08-05 23:24:58 +02:00
|
|
|
if (getCallbackToUse() != null) {
|
2015-04-19 02:37:29 +02:00
|
|
|
// only propagate the successful event
|
2015-08-05 23:24:58 +02:00
|
|
|
getCallbackToUse().onServicesDiscovered(gatt);
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
} else {
|
2015-05-12 06:28:11 +02:00
|
|
|
LOG.warn("onServicesDiscovered received: " + status);
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@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));
|
2015-05-22 23:15:45 +02:00
|
|
|
if (!checkCorrectGattInstance(gatt, "characteristic write")) {
|
|
|
|
return;
|
|
|
|
}
|
2015-08-05 23:24:58 +02:00
|
|
|
if (getCallbackToUse() != null) {
|
|
|
|
getCallbackToUse().onCharacteristicWrite(gatt, characteristic, status);
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
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));
|
2015-05-22 23:15:45 +02:00
|
|
|
if (!checkCorrectGattInstance(gatt, "characteristic read")) {
|
|
|
|
return;
|
|
|
|
}
|
2015-08-05 23:24:58 +02:00
|
|
|
if (getCallbackToUse() != null) {
|
2015-10-04 00:00:53 +02:00
|
|
|
try {
|
|
|
|
getCallbackToUse().onCharacteristicRead(gatt, characteristic, status);
|
|
|
|
} catch (Throwable ex) {
|
2015-10-18 17:29:41 +02:00
|
|
|
LOG.error("onCharacteristicRead: " + ex.getMessage(), ex);
|
2015-10-04 00:00:53 +02:00
|
|
|
}
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
checkWaitingCharacteristic(characteristic, status);
|
|
|
|
}
|
|
|
|
|
2015-05-24 14:39:36 +02:00
|
|
|
@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));
|
2015-05-24 14:39:36 +02:00
|
|
|
if (!checkCorrectGattInstance(gatt, "descriptor read")) {
|
|
|
|
return;
|
|
|
|
}
|
2015-08-05 23:24:58 +02:00
|
|
|
if (getCallbackToUse() != null) {
|
2015-10-04 00:00:53 +02:00
|
|
|
try {
|
|
|
|
getCallbackToUse().onDescriptorRead(gatt, descriptor, status);
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
LOG.error("onDescriptorRead: " + ex.getMessage(), ex);
|
|
|
|
}
|
2015-05-24 14:39:36 +02:00
|
|
|
}
|
|
|
|
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));
|
2015-05-24 14:39:36 +02:00
|
|
|
if (!checkCorrectGattInstance(gatt, "descriptor write")) {
|
|
|
|
return;
|
|
|
|
}
|
2015-08-05 23:24:58 +02:00
|
|
|
if (getCallbackToUse() != null) {
|
2015-10-04 00:00:53 +02:00
|
|
|
try {
|
|
|
|
getCallbackToUse().onDescriptorWrite(gatt, descriptor, status);
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
LOG.error("onDescriptorWrite: " + ex.getMessage(), ex);
|
|
|
|
}
|
2015-05-24 14:39:36 +02:00
|
|
|
}
|
|
|
|
checkWaitingCharacteristic(descriptor.getCharacteristic(), status);
|
|
|
|
}
|
|
|
|
|
2015-04-19 02:37:29 +02:00
|
|
|
@Override
|
|
|
|
public void onCharacteristicChanged(BluetoothGatt gatt,
|
|
|
|
BluetoothGattCharacteristic characteristic) {
|
2015-10-19 20:48:39 +02:00
|
|
|
if (LOG.isDebugEnabled()) {
|
2017-10-19 21:52:38 +02:00
|
|
|
String content = Logging.formatBytes(characteristic.getValue());
|
2016-02-29 20:54:39 +01:00
|
|
|
LOG.debug("characteristic changed: " + characteristic.getUuid() + " value: " + content);
|
2015-10-19 15:16:21 +02:00
|
|
|
}
|
2015-05-22 23:15:45 +02:00
|
|
|
if (!checkCorrectGattInstance(gatt, "characteristic changed")) {
|
|
|
|
return;
|
|
|
|
}
|
2015-08-05 23:24:58 +02:00
|
|
|
if (getCallbackToUse() != null) {
|
2015-10-04 00:00:53 +02:00
|
|
|
try {
|
|
|
|
getCallbackToUse().onCharacteristicChanged(gatt, characteristic);
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
LOG.error("onCharaceristicChanged: " + ex.getMessage(), ex);
|
|
|
|
}
|
2015-09-09 23:23:38 +02:00
|
|
|
} else {
|
|
|
|
LOG.info("No gattcallback registered, ignoring characteristic change");
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@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));
|
2015-05-22 23:15:45 +02:00
|
|
|
if (!checkCorrectGattInstance(gatt, "remote rssi")) {
|
|
|
|
return;
|
|
|
|
}
|
2015-08-05 23:24:58 +02:00
|
|
|
if (getCallbackToUse() != null) {
|
2015-10-04 00:00:53 +02:00
|
|
|
try {
|
|
|
|
getCallbackToUse().onReadRemoteRssi(gatt, rssi, status);
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
LOG.error("onReadRemoteRssi: " + ex.getMessage(), ex);
|
|
|
|
}
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
}
|
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) {
|
2018-09-23 11:27:35 +02:00
|
|
|
if (characteristic != null) {
|
|
|
|
LOG.debug("failed btle action, aborting transaction: " + characteristic.getUuid() + getStatusString(status));
|
|
|
|
}
|
2015-04-19 02:37:29 +02:00
|
|
|
mAbortTransaction = true;
|
|
|
|
}
|
|
|
|
if (characteristic != null && BtLEQueue.this.mWaitCharacteristic != null && characteristic.getUuid().equals(BtLEQueue.this.mWaitCharacteristic.getUuid())) {
|
|
|
|
if (mWaitForActionResultLatch != null) {
|
|
|
|
mWaitForActionResultLatch.countDown();
|
|
|
|
}
|
2015-05-17 21:55:02 +02:00
|
|
|
} else {
|
|
|
|
if (BtLEQueue.this.mWaitCharacteristic != null) {
|
2015-11-23 22:46:12 +01:00
|
|
|
LOG.error("checkWaitingCharacteristic: mismatched characteristic received: " + ((characteristic != null && characteristic.getUuid() != null) ? characteristic.getUuid().toString() : "(null)"));
|
2015-05-17 21:55:02 +02:00
|
|
|
}
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|
|
|
|
}
|
2015-05-25 23:14:02 +02:00
|
|
|
|
2015-08-05 23:24:58 +02:00
|
|
|
private String getStatusString(int status) {
|
|
|
|
return status == BluetoothGatt.GATT_SUCCESS ? " (success)" : " (failed: " + status + ")";
|
|
|
|
}
|
|
|
|
|
|
|
|
public void reset() {
|
2015-09-09 23:39:57 +02:00
|
|
|
if (LOG.isDebugEnabled()) {
|
|
|
|
LOG.debug("internal gatt callback set to null");
|
|
|
|
}
|
2015-08-05 23:24:58 +02:00
|
|
|
mTransactionGattCallback = null;
|
|
|
|
}
|
2015-09-24 14:45:21 +02:00
|
|
|
}
|
2015-04-19 02:37:29 +02:00
|
|
|
}
|