From 6577ad69b08fcae7c01502c3250f72b29d13812e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hler?= Date: Tue, 29 Jan 2019 11:00:58 +0100 Subject: [PATCH 1/4] Add initial support for Gatt server in BtLEQueue --- .../btle/AbstractBTLEDeviceSupport.java | 56 +++- .../gadgetbridge/service/btle/BtLEQueue.java | 278 +++++++++++++++--- .../service/btle/BtLEServerAction.java | 75 +++++ .../service/btle/GattServerCallback.java | 60 ++++ .../service/btle/ServerTransaction.java | 83 ++++++ .../btle/ServerTransactionBuilder.java | 87 ++++++ .../btle/actions/ServerResponseAction.java | 72 +++++ 7 files changed, 671 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEServerAction.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattServerCallback.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransactionBuilder.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ServerResponseAction.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java index 3c95bd61e..bdad45b77 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java @@ -17,6 +17,7 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.btle; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; @@ -44,16 +45,17 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBlePro * Bluetooth Smart. *

* The connection to the device and all communication is made with a generic {@link BtLEQueue}. - * Messages to the device are encoded as {@link BtLEAction actions} that are grouped with a - * {@link Transaction} and sent via {@link BtLEQueue}. + * Messages to the device are encoded as {@link BtLEAction actions} or {@link BtLEServerAction actions} + * that are grouped with a {@link Transaction} or {@link ServerTransaction} and sent via {@link BtLEQueue}. * * @see TransactionBuilder * @see BtLEQueue */ -public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport implements GattCallback { +public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport implements GattCallback, GattServerCallback { private BtLEQueue mQueue; private Map mAvailableCharacteristics; private final Set mSupportedServices = new HashSet<>(4); + private final Set mSupportedServerServices = new HashSet<>(4); private Logger logger; private final List> mSupportedProfiles = new ArrayList<>(); @@ -70,7 +72,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im @Override public boolean connect() { if (mQueue == null) { - mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, getContext()); + mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, this, getContext(), mSupportedServerServices); mQueue.setAutoReconnect(getAutoReconnect()); } return mQueue.connect(); @@ -136,6 +138,19 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im return createTransactionBuilder(taskName); } + public ServerTransactionBuilder createServerTransactionBuilder(String taskName) { + return new ServerTransactionBuilder(taskName); + } + + public ServerTransactionBuilder performServer(String taskName) throws IOException { + if (!isConnected()) { + if(!connect()) { + throw new IOException("1: Unable to connect to device: " + getDevice()); + } + } + return createServerTransactionBuilder(taskName); + } + /** * Ensures that the device is connected and (only then) performs the actions of the given * transaction builder. @@ -187,6 +202,14 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im mSupportedProfiles.add(profile); } + /** + * Subclasses should call this method to add server services they support. + * @param service + */ + protected void addSupportedServerService(BluetoothGattService service) { + mSupportedServerServices.add(service); + } + /** * Returns the characteristic matching the given UUID. Only characteristics * are returned whose service is marked as supported. @@ -337,4 +360,29 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im public void onSetLedColor(int color) { } + + @Override + public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { + + } + + @Override + public boolean onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { + return false; + } + + @Override + public boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { + return false; + } + + @Override + public boolean onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) { + return false; + } + + @Override + public boolean onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { + return false; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index 2d0d09169..28b9e16ab 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -23,7 +23,10 @@ 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; @@ -35,9 +38,10 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.BlockingQueue; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.LinkedBlockingQueue; import androidx.annotation.Nullable; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -53,20 +57,27 @@ public final class BtLEQueue { private static final Logger LOG = LoggerFactory.getLogger(BtLEQueue.class); private final Object mGattMonitor = new Object(); + private final Object mTransactionMonitor = new Object(); private final GBDevice mGbDevice; private final BluetoothAdapter mBluetoothAdapter; private BluetoothGatt mBluetoothGatt; + private BluetoothGattServer mBluetoothGattServer; + private final Set mSupportedServerServices; - private final BlockingQueue mTransactions = new LinkedBlockingQueue<>(); + private final Queue mTransactions = new ConcurrentLinkedQueue<>(); + private final Queue mServerTransactions = new ConcurrentLinkedQueue<>(); private volatile boolean mDisposed; private volatile boolean mCrashed; private volatile boolean mAbortTransaction; + private volatile boolean mAbortServerTransaction; 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 Thread dispatchThread = new Thread("Gadgetbridge GATT Dispatcher") { @@ -77,7 +88,16 @@ public final class BtLEQueue { while (!mDisposed && !mCrashed) { try { - Transaction transaction = mTransactions.take(); + LOG.info("waiting..."); + synchronized (mTransactionMonitor) { + try { + mTransactionMonitor.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + Transaction transaction = mTransactions.poll(); + ServerTransaction serverTransaction = mServerTransactions.poll(); if (!isConnected()) { LOG.debug("not connected, waiting for connection..."); @@ -94,37 +114,68 @@ public final class BtLEQueue { mConnectionLatch = null; } - 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; - } - 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; - } + if(serverTransaction != null) { + 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 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(transaction != null) { + 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; + } + 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 } - } else { - LOG.error("Action returned false: " + action); - break; // abort the transaction } } } catch (InterruptedException ignored) { @@ -143,11 +194,13 @@ public final class BtLEQueue { } }; - public BtLEQueue(BluetoothAdapter bluetoothAdapter, GBDevice gbDevice, GattCallback externalGattCallback, Context context) { + public BtLEQueue(BluetoothAdapter bluetoothAdapter, GBDevice gbDevice, GattCallback externalGattCallback, GattServerCallback externalGattServerCallback, Context context, Set supportedServerServices) { mBluetoothAdapter = bluetoothAdapter; mGbDevice = gbDevice; internalGattCallback = new InternalGattCallback(externalGattCallback); + internalGattServerCallback = new InternalGattServerCallback(externalGattServerCallback); mContext = context; + mSupportedServerServices = supportedServerServices; dispatchThread.start(); } @@ -183,6 +236,21 @@ public final class BtLEQueue { 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()) { @@ -218,6 +286,12 @@ public final class BtLEQueue { gatt.close(); setDeviceConnectionState(State.NOT_CONNECTED); } + BluetoothGattServer gattServer = mBluetoothGattServer; + if (gattServer != null) { + mBluetoothGattServer = null; + gattServer.clearServices(); + gattServer.close(); + } } } @@ -226,9 +300,16 @@ public final class BtLEQueue { internalGattCallback.reset(); mTransactions.clear(); mAbortTransaction = true; + mAbortServerTransaction = true; if (mWaitForActionResultLatch != null) { mWaitForActionResultLatch.countDown(); } + if (mWaitForServerActionResultLatch != null) { + mWaitForServerActionResultLatch.countDown(); + } + synchronized(mTransactionMonitor) { + mTransactionMonitor.notify(); + } boolean wasInitialized = mGbDevice.isInitialized(); setDeviceConnectionState(State.NOT_CONNECTED); @@ -286,6 +367,24 @@ public final class BtLEQueue { LOG.debug("about to add: " + transaction); if (!transaction.isEmpty()) { mTransactions.add(transaction); + synchronized(mTransactionMonitor) { + mTransactionMonitor.notify(); + } + } + } + + /** + * 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()) { + mServerTransactions.add(transaction); + synchronized(mTransactionMonitor) { + mTransactionMonitor.notify(); + } } } @@ -300,14 +399,25 @@ public final class BtLEQueue { LOG.debug("about to insert: " + transaction); if (!transaction.isEmpty()) { List tail = new ArrayList<>(mTransactions.size() + 2); - mTransactions.drainTo(tail); + //mTransactions.drainTo(tail); + for( Transaction t : mTransactions) { + tail.add(t); + } + mTransactions.clear(); mTransactions.add(transaction); mTransactions.addAll(tail); + synchronized(mTransactionMonitor) { + mTransactionMonitor.notify(); + } } } public void clear() { mTransactions.clear(); + mServerTransactions.clear(); + synchronized(mTransactionMonitor) { + mTransactionMonitor.notify(); + } } /** @@ -332,6 +442,16 @@ public final class BtLEQueue { 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 { @@ -549,4 +669,90 @@ public final class BtLEQueue { 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()); + if (getCallbackToUse() != null) { + getCallbackToUse().onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); + } + } + + @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()); + if(getCallbackToUse() != null) { + getCallbackToUse().onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value); + } + } + } + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEServerAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEServerAction.java new file mode 100644 index 000000000..f5aab7286 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEServerAction.java @@ -0,0 +1,75 @@ +/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.btle; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattServer; +import android.bluetooth.BluetoothGattService; + +import java.util.Date; + +import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; + +/** + * The Bluedroid implementation only allows performing one GATT request at a time. + * As they are asynchronous anyway, we encapsulate every GATT request (read and write) + * inside a runnable action. + *

+ * These actions are then executed one after another, ensuring that every action's result + * has been posted before invoking the next action. + */ +public abstract class BtLEServerAction { + private final BluetoothDevice device; + private final long creationTimestamp; + + public BtLEServerAction(BluetoothDevice device) { + this.device = device; + creationTimestamp = System.currentTimeMillis(); + } + + + public BluetoothDevice getDevice() { + return this.device; + } + + /** + * Returns true if this action expects an (async) result which must + * be waited for, before continuing with other actions. + *

+ * This is needed because the current Bluedroid stack can only deal + * with one single bluetooth operation at a time. + */ + public abstract boolean expectsResult(); + + /** + * Executes this action, e.g. reads or write a GATT characteristic. + * + * @return true if the action was successful, false otherwise + */ + public abstract boolean run(BluetoothGattServer server); + + + protected String getCreationTime() { + return DateTimeUtils.formatDateTime(new Date(creationTimestamp)); + } + + public String toString() { + return getCreationTime() + ":" + getClass().getSimpleName() + " on device: " + getDevice().getAddress(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattServerCallback.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattServerCallback.java new file mode 100644 index 000000000..03db77b44 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/GattServerCallback.java @@ -0,0 +1,60 @@ +package nodomain.freeyourgadget.gadgetbridge.service.btle; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattServerCallback; + +public interface GattServerCallback { + + /** + * @param device + * @param status + * @param newState + * @see BluetoothGattServerCallback#onConnectionStateChange(BluetoothDevice, int, int) + */ + void onConnectionStateChange(BluetoothDevice device, int status, int newState); + + /** + * @param device + * @param requestId + * @param offset + * @param characteristic + * @see BluetoothGattServerCallback#onCharacteristicReadRequest(BluetoothDevice, int, int, BluetoothGattCharacteristic) + */ + boolean onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic); + + /** + * @param device + * @param requestId + * @param characteristic + * @param preparedWrite + * @param responseNeeded + * @param offset + * @param value + * @see BluetoothGattServerCallback#onCharacteristicWriteRequest(BluetoothDevice, int, BluetoothGattCharacteristic, boolean, boolean, int, byte[]) + */ + boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value); + + /** + * @param device + * @param requestId + * @param offset + * @param descriptor + * @see BluetoothGattServerCallback#onDescriptorReadRequest(BluetoothDevice, int, int, BluetoothGattDescriptor) + */ + boolean onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor); + + /** + * @param device + * @param requestId + * @param descriptor + * @param preparedWrite + * @param responseNeeded + * @param offset + * @param value + * @see BluetoothGattServerCallback#onDescriptorWriteRequest(BluetoothDevice, int, BluetoothGattDescriptor, boolean, boolean, int, byte[]) + */ + boolean onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value); + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java new file mode 100644 index 000000000..701aec798 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java @@ -0,0 +1,83 @@ +/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.btle; + +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import androidx.annotation.Nullable; + +/** + * Groups a bunch of {@link BtLEServerAction actions} together, making sure + * that upon failure of one action, all subsequent actions are discarded. + * + * @author TREND + */ +public class ServerTransaction { + private final String mName; + private final List mActions = new ArrayList<>(4); + private final long creationTimestamp = System.currentTimeMillis(); + private + @Nullable + GattServerCallback gattCallback; + + public ServerTransaction(String taskName) { + this.mName = taskName; + } + + public String getTaskName() { + return mName; + } + + public void add(BtLEServerAction action) { + mActions.add(action); + } + + public List getActions() { + return Collections.unmodifiableList(mActions); + } + + public boolean isEmpty() { + return mActions.isEmpty(); + } + + protected String getCreationTime() { + return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(creationTimestamp)); + } + + @Override + public String toString() { + return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), mActions.size()); + } + + public void setGattCallback(@Nullable GattServerCallback callback) { + gattCallback = callback; + } + + /** + * Returns the GattServerCallback for this transaction, or null if none. + */ + public + @Nullable + GattServerCallback getGattCallback() { + return gattCallback; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransactionBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransactionBuilder.java new file mode 100644 index 000000000..5a8e7693d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransactionBuilder.java @@ -0,0 +1,87 @@ +/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.btle; + +import android.bluetooth.BluetoothDevice; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import androidx.annotation.Nullable; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ServerResponseAction; + +public class ServerTransactionBuilder { + private static final Logger LOG = LoggerFactory.getLogger(ServerTransactionBuilder.class); + + private final ServerTransaction mTransaction; + private boolean mQueued; + + public ServerTransactionBuilder(String taskName) { + mTransaction = new ServerTransaction(taskName); + } + + public ServerTransactionBuilder writeServerResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] data) { + if(device == null) { + LOG.warn("Unable to write to device: null"); + return this; + } + ServerResponseAction action = new ServerResponseAction(device, requestId, status, offset, data); + return add(action); + } + + public ServerTransactionBuilder add(BtLEServerAction action) { + mTransaction.add(action); + return this; + } + + /** + * Sets a GattServerCallback instance that will be called when the transaction is executed, + * resulting in GattServerCallback events. + * + * @param callback the callback to set, may be null + */ + public void setGattCallback(@Nullable GattServerCallback callback) { + mTransaction.setGattCallback(callback); + } + + public + @Nullable + GattServerCallback getGattCallback() { + return mTransaction.getGattCallback(); + } + + /** + * To be used as the final step to execute the transaction by the given queue. + * + * @param queue + */ + public void queue(BtLEQueue queue) { + if (mQueued) { + throw new IllegalStateException("This builder had already been queued. You must not reuse it."); + } + mQueued = true; + queue.add(mTransaction); + } + + public ServerTransaction getTransaction() { + return mTransaction; + } + + public String getTaskName() { + return mTransaction.getTaskName(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ServerResponseAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ServerResponseAction.java new file mode 100644 index 000000000..817c25f32 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ServerResponseAction.java @@ -0,0 +1,72 @@ +/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.btle.actions; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattServer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.Logging; +import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEServerAction; + +/** + * Invokes a response on a given GATT characteristic read. + * The result status will be made available asynchronously through the + * {@link BluetoothGattCallback} + */ +public class ServerResponseAction extends BtLEServerAction { + private static final Logger LOG = LoggerFactory.getLogger(ServerResponseAction.class); + + private final byte[] value; + private final int requestId; + private final int status; + private final int offset; + + public ServerResponseAction(BluetoothDevice device, int requestId, int status, int offset, byte[] data) { + super(device); + this.value = data; + this.requestId = requestId; + this.status = status; + this.offset = offset; + } + + @Override + public boolean run(BluetoothGattServer server) { + return writeValue(server, getDevice(), requestId, status, offset, value); + } + + protected boolean writeValue(BluetoothGattServer gattServer, BluetoothDevice device, int requestId, int status, int offset, byte[] value) { + if (LOG.isDebugEnabled()) { + LOG.debug("writing to server: " + device.getAddress() + ": " + Logging.formatBytes(value)); + } + + return gattServer.sendResponse(device, requestId, 0, offset, value); + } + + protected final byte[] getValue() { + return value; + } + + @Override + public boolean expectsResult() { + return false; + } +} From 88ac816393d30014df62819fd36f1d78a1c5729d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hler?= Date: Tue, 5 Feb 2019 18:46:54 +0100 Subject: [PATCH 2/4] Fix queue handling if only server or client operation is present --- .../gadgetbridge/service/btle/BtLEQueue.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index 28b9e16ab..9f8da69f6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -88,12 +88,13 @@ public final class BtLEQueue { while (!mDisposed && !mCrashed) { try { - LOG.info("waiting..."); - synchronized (mTransactionMonitor) { - try { - mTransactionMonitor.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); + if(mTransactions.isEmpty() && mServerTransactions.isEmpty()) { + synchronized (mTransactionMonitor) { + try { + mTransactionMonitor.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } } } Transaction transaction = mTransactions.poll(); From 325add3f0aa2b6deb6e8e70a0c549da5446c5872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hler?= Date: Wed, 27 Feb 2019 09:21:41 +0100 Subject: [PATCH 3/4] Remove duplicate queue # Conflicts: # app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java --- .../service/btle/AbstractTransaction.java | 49 ++++++++++++++++++ .../gadgetbridge/service/btle/BtLEQueue.java | 50 +++++-------------- .../service/btle/ServerTransaction.java | 21 +++----- .../service/btle/Transaction.java | 27 +++------- 4 files changed, 76 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java new file mode 100644 index 000000000..68da4be31 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java @@ -0,0 +1,49 @@ +/* Copyright (C) 2015-2019 Andreas Shimokawa, Carsten Pfeiffer, Andreas Boehler + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.btle; + +import java.text.DateFormat; +import java.util.Date; +import java.util.Locale; + +public abstract class AbstractTransaction { + private final String mName; + private final long creationTimestamp = System.currentTimeMillis(); + + + public AbstractTransaction(String taskName) { + this.mName = taskName; + } + + public String getTaskName() { + return mName; + } + + protected String getCreationTime() { + return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(creationTimestamp)); + } + + public int getActionSize() { + return 0; + } + + @Override + public String toString() { + return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), getActionSize()); + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index 9f8da69f6..e0d6941f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -38,10 +38,10 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Queue; import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; import androidx.annotation.Nullable; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -57,15 +57,13 @@ public final class BtLEQueue { private static final Logger LOG = LoggerFactory.getLogger(BtLEQueue.class); private final Object mGattMonitor = new Object(); - private final Object mTransactionMonitor = new Object(); private final GBDevice mGbDevice; private final BluetoothAdapter mBluetoothAdapter; private BluetoothGatt mBluetoothGatt; private BluetoothGattServer mBluetoothGattServer; private final Set mSupportedServerServices; - private final Queue mTransactions = new ConcurrentLinkedQueue<>(); - private final Queue mServerTransactions = new ConcurrentLinkedQueue<>(); + private final BlockingQueue mTransactions = new LinkedBlockingQueue<>(); private volatile boolean mDisposed; private volatile boolean mCrashed; private volatile boolean mAbortTransaction; @@ -88,17 +86,7 @@ public final class BtLEQueue { while (!mDisposed && !mCrashed) { try { - if(mTransactions.isEmpty() && mServerTransactions.isEmpty()) { - synchronized (mTransactionMonitor) { - try { - mTransactionMonitor.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - Transaction transaction = mTransactions.poll(); - ServerTransaction serverTransaction = mServerTransactions.poll(); + AbstractTransaction qTransaction = mTransactions.take(); if (!isConnected()) { LOG.debug("not connected, waiting for connection..."); @@ -115,7 +103,8 @@ public final class BtLEQueue { mConnectionLatch = null; } - if(serverTransaction != null) { + if(qTransaction instanceof ServerTransaction) { + ServerTransaction serverTransaction = (ServerTransaction)qTransaction; internalGattServerCallback.setTransactionGattCallback(serverTransaction.getGattCallback()); mAbortServerTransaction = false; @@ -144,7 +133,8 @@ public final class BtLEQueue { } } - if(transaction != null) { + if(qTransaction instanceof Transaction) { + Transaction transaction = (Transaction)qTransaction; internalGattCallback.setTransactionGattCallback(transaction.getGattCallback()); mAbortTransaction = false; // Run all actions of the transaction until one doesn't succeed @@ -308,10 +298,9 @@ public final class BtLEQueue { if (mWaitForServerActionResultLatch != null) { mWaitForServerActionResultLatch.countDown(); } - synchronized(mTransactionMonitor) { - mTransactionMonitor.notify(); - } + boolean wasInitialized = mGbDevice.isInitialized(); + setDeviceConnectionState(State.NOT_CONNECTED); // either we've been disconnected because the device is out of range @@ -368,9 +357,6 @@ public final class BtLEQueue { LOG.debug("about to add: " + transaction); if (!transaction.isEmpty()) { mTransactions.add(transaction); - synchronized(mTransactionMonitor) { - mTransactionMonitor.notify(); - } } } @@ -382,10 +368,7 @@ public final class BtLEQueue { public void add(ServerTransaction transaction) { LOG.debug("about to add: " + transaction); if(!transaction.isEmpty()) { - mServerTransactions.add(transaction); - synchronized(mTransactionMonitor) { - mTransactionMonitor.notify(); - } + mTransactions.add(transaction); } } @@ -399,26 +382,19 @@ public final class BtLEQueue { public void insert(Transaction transaction) { LOG.debug("about to insert: " + transaction); if (!transaction.isEmpty()) { - List tail = new ArrayList<>(mTransactions.size() + 2); + List tail = new ArrayList<>(mTransactions.size() + 2); //mTransactions.drainTo(tail); - for( Transaction t : mTransactions) { + for( AbstractTransaction t : mTransactions) { tail.add(t); } mTransactions.clear(); mTransactions.add(transaction); mTransactions.addAll(tail); - synchronized(mTransactionMonitor) { - mTransactionMonitor.notify(); - } } } public void clear() { mTransactions.clear(); - mServerTransactions.clear(); - synchronized(mTransactionMonitor) { - mTransactionMonitor.notify(); - } } /** diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java index 701aec798..f1dbe2a5a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java @@ -16,10 +16,8 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.btle; -import java.text.DateFormat; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.Locale; @@ -31,20 +29,14 @@ import androidx.annotation.Nullable; * * @author TREND */ -public class ServerTransaction { - private final String mName; +public class ServerTransaction extends AbstractTransaction { private final List mActions = new ArrayList<>(4); - private final long creationTimestamp = System.currentTimeMillis(); private @Nullable GattServerCallback gattCallback; public ServerTransaction(String taskName) { - this.mName = taskName; - } - - public String getTaskName() { - return mName; + super(taskName); } public void add(BtLEServerAction action) { @@ -59,10 +51,6 @@ public class ServerTransaction { return mActions.isEmpty(); } - protected String getCreationTime() { - return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(creationTimestamp)); - } - @Override public String toString() { return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), mActions.size()); @@ -80,4 +68,9 @@ public class ServerTransaction { GattServerCallback getGattCallback() { return gattCallback; } + + @Override + public int getActionSize() { + return mActions.size(); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java index ba91feca7..1dc0ecabf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java @@ -17,12 +17,9 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.btle; -import java.text.DateFormat; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.List; -import java.util.Locale; import androidx.annotation.Nullable; @@ -32,20 +29,14 @@ import androidx.annotation.Nullable; * * @author TREND */ -public class Transaction { - private final String mName; +public class Transaction extends AbstractTransaction { private final List mActions = new ArrayList<>(4); - private final long creationTimestamp = System.currentTimeMillis(); private @Nullable GattCallback gattCallback; public Transaction(String taskName) { - this.mName = taskName; - } - - public String getTaskName() { - return mName; + super(taskName); } public void add(BtLEAction action) { @@ -60,15 +51,6 @@ public class Transaction { return mActions.isEmpty(); } - protected String getCreationTime() { - return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(creationTimestamp)); - } - - @Override - public String toString() { - return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), mActions.size()); - } - public void setGattCallback(@Nullable GattCallback callback) { gattCallback = callback; } @@ -81,4 +63,9 @@ public class Transaction { GattCallback getGattCallback() { return gattCallback; } + + @Override + public int getActionSize() { + return mActions.size(); + } } From c091828205087bf0c7d007c851c6c1f6c2f5eb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hler?= Date: Wed, 27 Feb 2019 19:07:53 +0100 Subject: [PATCH 4/4] Rename getActionSize() -> getActionCount() and make it abstract --- .../service/btle/AbstractTransaction.java | 7 +- .../service/btle/ServerTransaction.java | 2 +- .../service/btle/Transaction.java | 2 +- .../devices/casiogb6900/CasioGATTServer.java | 228 ------------------ 4 files changed, 4 insertions(+), 235 deletions(-) delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java index 68da4be31..0ab0d611c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java @@ -24,7 +24,6 @@ public abstract class AbstractTransaction { private final String mName; private final long creationTimestamp = System.currentTimeMillis(); - public AbstractTransaction(String taskName) { this.mName = taskName; } @@ -37,13 +36,11 @@ public abstract class AbstractTransaction { return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(creationTimestamp)); } - public int getActionSize() { - return 0; - } + public abstract int getActionCount(); @Override public String toString() { - return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), getActionSize()); + return String.format(Locale.US, "%s: Transaction task: %s with %d actions", getCreationTime(), getTaskName(), getActionCount()); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java index f1dbe2a5a..bf4cad8df 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java @@ -70,7 +70,7 @@ public class ServerTransaction extends AbstractTransaction { } @Override - public int getActionSize() { + public int getActionCount() { return mActions.size(); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java index 1dc0ecabf..eef19df95 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java @@ -65,7 +65,7 @@ public class Transaction extends AbstractTransaction { } @Override - public int getActionSize() { + public int getActionCount() { return mActions.size(); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java deleted file mode 100644 index 3820ab398..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java +++ /dev/null @@ -1,228 +0,0 @@ -/* Copyright (C) 2018-2019 Andreas Böhler, Daniele Gobbetti - based on code from BlueWatcher, https://github.com/masterjc/bluewatcher - - 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 . */ -package nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900; - -import android.bluetooth.BluetoothDevice; -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.content.Context; -import android.content.Intent; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; -import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; -import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; - -class CasioGATTServer extends BluetoothGattServerCallback { - private static final Logger LOG = LoggerFactory.getLogger(CasioGATTServer.class); - - private Context mContext; - private BluetoothGattServer mBluetoothGattServer; - private CasioGB6900DeviceSupport mDeviceSupport = null; - private final GBDeviceEventMusicControl musicCmd = new GBDeviceEventMusicControl(); - - CasioGATTServer(Context context, CasioGB6900DeviceSupport deviceSupport) { - mContext = context; - mDeviceSupport = deviceSupport; - } - - public void setContext(Context ctx) { - mContext = ctx; - } - - boolean initialize() { - if(mContext == null) { - return false; - } - - BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE); - if (bluetoothManager == null) { - return false; - } - mBluetoothGattServer = bluetoothManager.openGattServer(mContext, this); - if (mBluetoothGattServer == null) { - return false; - } - - BluetoothGattService casioGATTService = new BluetoothGattService(CasioGB6900Constants.WATCH_CTRL_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); - BluetoothGattCharacteristic bluetoothgGATTCharacteristic = new BluetoothGattCharacteristic(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE); - bluetoothgGATTCharacteristic.setValue(new byte[0]); - - BluetoothGattCharacteristic bluetoothgGATTCharacteristic2 = new BluetoothGattCharacteristic(CasioGB6900Constants.NAME_OF_APP_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED); - bluetoothgGATTCharacteristic2.setValue(CasioGB6900Constants.MUSIC_MESSAGE.getBytes()); - - BluetoothGattDescriptor bluetoothGattDescriptor = new BluetoothGattDescriptor(CasioGB6900Constants.CCC_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE); - bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - - bluetoothgGATTCharacteristic2.addDescriptor(bluetoothGattDescriptor); - - casioGATTService.addCharacteristic(bluetoothgGATTCharacteristic); - casioGATTService.addCharacteristic(bluetoothgGATTCharacteristic2); - mBluetoothGattServer.addService(casioGATTService); - - return true; - } - - @Override - public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { - - if (!characteristic.getUuid().equals(CasioGB6900Constants.NAME_OF_APP_CHARACTERISTIC_UUID)) { - LOG.warn("unexpected read request"); - return; - } - - LOG.info("will send response to read request from device: " + device.getAddress()); - - if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, CasioGB6900Constants.MUSIC_MESSAGE.getBytes())) { - LOG.warn("error sending response"); - } - } - private GBDeviceEventMusicControl.Event parse3Button(int button) { - GBDeviceEventMusicControl.Event event; - switch(button) { - case 3: - event = GBDeviceEventMusicControl.Event.NEXT; - break; - case 2: - event = GBDeviceEventMusicControl.Event.PREVIOUS; - break; - case 1: - event = GBDeviceEventMusicControl.Event.PLAYPAUSE; - break; - default: - LOG.warn("Unhandled button received: " + button); - event = GBDeviceEventMusicControl.Event.UNKNOWN; - } - return event; - } - - private GBDeviceEventMusicControl.Event parse2Button(int button) { - GBDeviceEventMusicControl.Event event; - switch(button) { - case 2: - event = GBDeviceEventMusicControl.Event.PLAYPAUSE; - break; - case 1: - event = GBDeviceEventMusicControl.Event.NEXT; - break; - default: - LOG.warn("Unhandled button received: " + button); - event = GBDeviceEventMusicControl.Event.UNKNOWN; - } - return event; - } - - @Override - public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, - boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { - - if (!characteristic.getUuid().equals(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID)) { - LOG.warn("unexpected write request"); - return; - } - - if(mDeviceSupport == null) { - LOG.warn("mDeviceSupport is null, did initialization complete?"); - return; - } - - if((value[0] & 0x03) == 0) { - int button = value[1] & 0x0f; - LOG.info("Button pressed: " + button); - switch(mDeviceSupport.getModel()) - { - case MODEL_CASIO_5600B: - musicCmd.event = parse2Button(button); - break; - case MODEL_CASIO_6900B: - musicCmd.event = parse3Button(button); - break; - case MODEL_CASIO_GENERIC: - musicCmd.event = parse3Button(button); - break; - default: - LOG.warn("Unhandled device"); - return; - } - mDeviceSupport.evaluateGBDeviceEvent(musicCmd); - mDeviceSupport.evaluateGBDeviceEvent(musicCmd); - } - else { - LOG.info("received from device: " + value.toString()); - } - } - - @Override - public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { - - LOG.info("Connection state change for device: " + device.getAddress() + " status = " + status + " newState = " + newState); - if (newState == BluetoothGattServer.STATE_DISCONNECTED) { - LOG.info("CASIO GATT server noticed disconnect."); - } - if (newState == BluetoothGattServer.STATE_CONNECTED) { - GBDevice.State devState = mDeviceSupport.getDevice().getState(); - Intent deviceCommunicationServiceIntent = new Intent(mContext, DeviceCommunicationService.class); - if (devState.equals(GBDevice.State.WAITING_FOR_RECONNECT) || devState.equals(GBDevice.State.NOT_CONNECTED)) { - LOG.info("Forcing re-connect because GATT server has been reconnected."); - deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_CONNECT); - deviceCommunicationServiceIntent.putExtra(GBDevice.EXTRA_DEVICE, device); - LocalBroadcastManager.getInstance(mContext).sendBroadcast(deviceCommunicationServiceIntent); - //PendingIntent reconnectPendingIntent = PendingIntent.getService(mContext, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT); - //builder.addAction(R.drawable.ic_notification, context.getString(R.string.controlcenter_connect), reconnectPendingIntent); - } - } - } - - @Override - public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, - boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { - - LOG.info("onDescriptorWriteRequest() notifications enabled = " + (value[0] == 1)); - if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, value)) { - LOG.warn("onDescriptorWriteRequest() error sending response!"); - } - } - - @Override - public void onServiceAdded(int status, BluetoothGattService service) { - LOG.info("onServiceAdded() status = " + status + " service = " + service.getUuid()); - } - - @Override - public void onNotificationSent(BluetoothDevice bluetoothDevice, int status) { - LOG.info("onNotificationSent() status = " + status + " to device " + bluetoothDevice.getAddress()); - } - - void close() { - if (mBluetoothGattServer != null) { - mBluetoothGattServer.clearServices(); - mBluetoothGattServer.close(); - mBluetoothGattServer = null; - } - } - -}