diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 05fa9114fe..18264b5ed8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -338,6 +338,13 @@
+
+
+
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothScanCallbackReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothScanCallbackReceiver.java
new file mode 100644
index 0000000000..c0b3be9219
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothScanCallbackReceiver.java
@@ -0,0 +1,80 @@
+/* Copyright (C) 2019 Andreas Böhler
+
+ 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.externalevents;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanResult;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+public class BluetoothScanCallbackReceiver extends BroadcastReceiver {
+
+ private static final Logger LOG = LoggerFactory.getLogger(BluetoothScanCallbackReceiver.class);
+ private String mSeenScanCallbackUUID = "";
+
+ @TargetApi(Build.VERSION_CODES.O)
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if(!action.equals("nodomain.freeyourgadget.gadgetbridge.blescancallback") || !intent.hasExtra("address") || !intent.hasExtra("uuid")) {
+ return;
+ }
+
+ String wantedAddress = intent.getExtras().getString("address");
+ String uuid = intent.getExtras().getString("uuid");
+
+ int bleCallbackType = intent.getIntExtra(BluetoothLeScanner.EXTRA_CALLBACK_TYPE, -1);
+ if(bleCallbackType != -1) {
+ //LOG.debug("Passive background scan callback type: " + bleCallbackType);
+ ArrayList scanResults = intent.getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT);
+ for(ScanResult result: scanResults) {
+ BluetoothDevice device = result.getDevice();
+ if(device.getAddress().equals(wantedAddress) && !mSeenScanCallbackUUID.equals(uuid)) {
+ mSeenScanCallbackUUID = uuid;
+ LOG.info("ScanCallbackReceiver has found " + device.getAddress() + "(" + device.getName() + ")");
+ BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner().stopScan(getScanCallbackIntent(GBApplication.getContext(), wantedAddress, uuid));
+ GBApplication.deviceService().connect(DeviceHelper.getInstance().toSupportedDevice(device));
+
+ }
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.O)
+ public static PendingIntent getScanCallbackIntent(Context context, String address, String uuid) {
+ Intent intent = new Intent(context, BluetoothScanCallbackReceiver.class);
+ intent.setAction("nodomain.freeyourgadget.gadgetbridge.blescancallback");
+ intent.putExtra("address", address);
+ intent.putExtra("uuid", uuid);
+ return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+}
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 3c95bd61e7..2d8bf6998c 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;
@@ -33,27 +34,30 @@
import java.util.Set;
import java.util.UUID;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
+import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
/**
* Abstract base class for all devices connected through Bluetooth Low Energy (LE) aka
* 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,8 +74,16 @@ public AbstractBTLEDeviceSupport(Logger logger) {
@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());
+ GBPrefs prefs = GBApplication.getGBPrefs();
+ boolean autoReconnectScan = GBPrefs.AUTO_RECONNECT_SCAN_DEFAULT;
+ if (prefs != null) {
+ autoReconnectScan = prefs.getAutoReconnectScan();
+ }
+ // Override the user preference if required by the device
+ autoReconnectScan = autoReconnectScan || useBleScannerForReconnect();
+ mQueue.setBleScannerForReconnect(autoReconnectScan);
}
return mQueue.connect();
}
@@ -136,6 +148,19 @@ public TransactionBuilder performInitialized(String taskName) throws IOException
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 +212,14 @@ protected void addSupportedProfile(AbstractBleProfile> profile) {
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 +370,33 @@ public void onSetFmFrequency(float frequency) {
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;
+ }
+
+ public boolean useBleScannerForReconnect() {
+ return false;
+ }
}
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 0000000000..0ab0d611cd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractTransaction.java
@@ -0,0 +1,46 @@
+/* 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 abstract int getActionCount();
+
+ @Override
+ public String toString() {
+ 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/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
index 2d0d091693..d135ab6576 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
@@ -17,15 +17,26 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.service.btle;
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
import android.content.Context;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -35,6 +46,8 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
@@ -42,6 +55,7 @@
import androidx.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothScanCallbackReceiver;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
@@ -56,18 +70,45 @@ public final class BtLEQueue {
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 BlockingQueue mTransactions = new LinkedBlockingQueue<>();
private volatile boolean mDisposed;
private volatile boolean mCrashed;
private volatile boolean mAbortTransaction;
+ private volatile boolean mAbortServerTransaction;
+
+ private final Handler mHandler = new Handler();
private final Context mContext;
private CountDownLatch mWaitForActionResultLatch;
+ private CountDownLatch mWaitForServerActionResultLatch;
private CountDownLatch mConnectionLatch;
private BluetoothGattCharacteristic mWaitCharacteristic;
private final InternalGattCallback internalGattCallback;
- private boolean mAutoReconnect;
+ private final InternalGattServerCallback internalGattServerCallback;
+ private boolean mAutoReconnect = false;
+
+ private BluetoothLeScanner mBluetoothScanner;
+ private boolean mUseBleScannerForReconnect = false;
+ private PendingIntent mScanCallbackIntent = null;
+
+ private Runnable mRestartRunnable = new Runnable() {
+ @Override
+ public void run() {
+ LOG.info("Restarting background scan due to Android N limitations...");
+ startBleBackgroundScan();
+ }
+ };
+
+ private Runnable mReduceBleScanIntervalRunnable = new Runnable() {
+ @Override
+ public void run() {
+ LOG.info("Restarting BLE background scan with lower priority...");
+ startBleBackgroundScan(false);
+ }
+ };
private Thread dispatchThread = new Thread("Gadgetbridge GATT Dispatcher") {
@@ -77,7 +118,7 @@ public void run() {
while (!mDisposed && !mCrashed) {
try {
- Transaction transaction = mTransactions.take();
+ AbstractTransaction qTransaction = mTransactions.take();
if (!isConnected()) {
LOG.debug("not connected, waiting for connection...");
@@ -94,37 +135,70 @@ public void run() {
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(qTransaction instanceof ServerTransaction) {
+ ServerTransaction serverTransaction = (ServerTransaction)qTransaction;
+ internalGattServerCallback.setTransactionGattCallback(serverTransaction.getGattCallback());
+ mAbortServerTransaction = false;
+
+ for (BtLEServerAction action : serverTransaction.getActions()) {
+ if (mAbortServerTransaction) { // got disconnected
+ LOG.info("Aborting running transaction");
+ break;
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("About to run 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 (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(qTransaction instanceof Transaction) {
+ Transaction transaction = (Transaction)qTransaction;
+ 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 +217,13 @@ public void run() {
}
};
- 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();
}
@@ -156,6 +232,10 @@ public void setAutoReconnect(boolean enable) {
mAutoReconnect = enable;
}
+ public void setBleScannerForReconnect(boolean enable) {
+ mUseBleScannerForReconnect = enable;
+ }
+
protected boolean isConnected() {
return mGbDevice.isConnected();
}
@@ -183,6 +263,21 @@ public boolean connect() {
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()) {
@@ -210,6 +305,24 @@ private void setDeviceConnectionState(State newState) {
public void disconnect() {
synchronized (mGattMonitor) {
LOG.debug("disconnect()");
+
+ BluetoothGattServer gattServer = mBluetoothGattServer;
+ if (gattServer != null) {
+ mBluetoothGattServer = null;
+ BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+ if (bluetoothManager == null) {
+ LOG.error("Error getting bluetoothManager");
+ } else {
+ List devices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER);
+ for(BluetoothDevice device : devices) {
+ LOG.debug("Disconnecting device: " + device.getAddress());
+ gattServer.cancelConnection(device);
+ }
+ }
+ gattServer.clearServices();
+ gattServer.close();
+ }
+
BluetoothGatt gatt = mBluetoothGatt;
if (gatt != null) {
mBluetoothGatt = null;
@@ -226,10 +339,14 @@ private void handleDisconnected(int status) {
internalGattCallback.reset();
mTransactions.clear();
mAbortTransaction = true;
+ mAbortServerTransaction = true;
if (mWaitForActionResultLatch != null) {
mWaitForActionResultLatch.countDown();
}
- boolean wasInitialized = mGbDevice.isInitialized();
+ if (mWaitForServerActionResultLatch != null) {
+ mWaitForServerActionResultLatch.countDown();
+ }
+
setDeviceConnectionState(State.NOT_CONNECTED);
// either we've been disconnected because the device is out of range
@@ -239,7 +356,7 @@ private void handleDisconnected(int status) {
// reconnecting automatically, so we try to fix this by re-creating mBluetoothGatt.
// Not sure if this actually works without re-initializing the device...
if (mBluetoothGatt != null) {
- if (!wasInitialized || !maybeReconnect()) {
+ if (!maybeReconnect()) {
disconnect(); // ensure that we start over cleanly next time
}
}
@@ -252,16 +369,122 @@ private void handleDisconnected(int status) {
*/
private boolean maybeReconnect() {
if (mAutoReconnect && mBluetoothGatt != null) {
- LOG.info("Enabling automatic ble reconnect...");
- boolean result = mBluetoothGatt.connect();
- if (result) {
- setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
+ if(!mUseBleScannerForReconnect) {
+ LOG.info("Enabling automatic ble reconnect...");
+ boolean result = mBluetoothGatt.connect();
+ if (result) {
+ setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
+ }
+ return result;
+ } else {
+ if(GBApplication.isRunningLollipopOrLater()) {
+ LOG.info("Enabling BLE background scan");
+ disconnect(); // ensure that we start over cleanly next time
+ startBleBackgroundScan();
+ setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
+ return true;
+ }
}
- return result;
}
return false;
}
+ @TargetApi(Build.VERSION_CODES.O)
+ PendingIntent getScanCallbackIntent(boolean newUuid) {
+ if(newUuid || mScanCallbackIntent == null) {
+ String uuid = UUID.randomUUID().toString();
+ mScanCallbackIntent = BluetoothScanCallbackReceiver.getScanCallbackIntent(mContext, mGbDevice.getAddress(), uuid);
+ }
+ return mScanCallbackIntent;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void stopBleBackgroundScan() {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ mHandler.removeCallbacks(mReduceBleScanIntervalRunnable);
+ if(mBluetoothScanner != null) {
+ mBluetoothScanner.stopScan(getScanCallbackIntent(false));
+ }
+ } else {
+ mHandler.removeCallbacks(mRestartRunnable);
+ if(mBluetoothScanner != null) {
+ mBluetoothScanner.stopScan(mScanCallback);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void startBleBackgroundScan() {
+ startBleBackgroundScan(true);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void startBleBackgroundScan(boolean highPowerMode) {
+ if(mBluetoothScanner == null)
+ mBluetoothScanner = mBluetoothAdapter.getBluetoothLeScanner();
+
+ ScanSettings settings;
+ if(highPowerMode) {
+ settings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+ .build();
+ } else {
+ settings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
+ .build();
+ }
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ LOG.info("Using Android O+ BLE scanner");
+ List filters = Collections.singletonList(new ScanFilter.Builder().build());
+ mBluetoothScanner.stopScan(getScanCallbackIntent(false));
+ mBluetoothScanner.startScan(filters, settings, getScanCallbackIntent(true));
+ // If high power mode is requested, we scan for 5 minutes
+ // and then continue scanning with lower priority (scan mode balanced) in order
+ // to conserve power.
+ if(highPowerMode) {
+ mHandler.postDelayed(mReduceBleScanIntervalRunnable, 5 * 60 * 1000);
+ }
+ }
+ else {
+ LOG.info("Using Android L-N BLE scanner");
+ List filters = Collections.singletonList(new ScanFilter.Builder().setDeviceAddress(mGbDevice.getAddress()).build());
+ mBluetoothScanner.stopScan(mScanCallback);
+ mBluetoothScanner.startScan(filters, settings, mScanCallback);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ mHandler.postDelayed(mRestartRunnable, 25 * 60 * 1000);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private ScanCallback mScanCallback = new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ String deviceName = result.getDevice().getName();
+ String deviceAddress = result.getDevice().getAddress();
+
+ LOG.info("Scanner: Found: " + deviceName + " " + deviceAddress);
+ // The filter already filtered for our specific device, so it is enough to connect to it
+ mBluetoothScanner.stopScan(mScanCallback);
+ mHandler.removeCallbacks(mRestartRunnable);
+ connect();
+ setDeviceConnectionState(State.CONNECTING);
+ }
+
+ @Override
+ public void onBatchScanResults(List results) {
+ for (ScanResult sr : results) {
+ LOG.info("ScanCallback.onBatchScanResults.each:" + sr.toString());
+ }
+ }
+
+ @Override
+ public void onScanFailed(int errorCode) {
+ LOG.error("ScanCallback.onScanFailed:" + errorCode);
+ }
+ };
+
public void dispose() {
if (mDisposed) {
return;
@@ -269,6 +492,9 @@ public void dispose() {
mDisposed = true;
// try {
disconnect();
+ if(mUseBleScannerForReconnect) {
+ stopBleBackgroundScan();
+ }
dispatchThread.interrupt();
dispatchThread = null;
// dispatchThread.join();
@@ -289,6 +515,18 @@ public void add(Transaction transaction) {
}
}
+ /**
+ * Adds a serverTransaction to the end of the queue
+ *
+ * @param transaction
+ */
+ public void add(ServerTransaction transaction) {
+ LOG.debug("about to add: " + transaction);
+ if(!transaction.isEmpty()) {
+ mTransactions.add(transaction);
+ }
+ }
+
/**
* Adds a transaction to the beginning of the queue.
* Note that actions of the *currently executing* transaction
@@ -299,8 +537,12 @@ public void add(Transaction transaction) {
public void insert(Transaction transaction) {
LOG.debug("about to insert: " + transaction);
if (!transaction.isEmpty()) {
- List tail = new ArrayList<>(mTransactions.size() + 2);
- mTransactions.drainTo(tail);
+ List tail = new ArrayList<>(mTransactions.size() + 2);
+ //mTransactions.drainTo(tail);
+ for( AbstractTransaction t : mTransactions) {
+ tail.add(t);
+ }
+ mTransactions.clear();
mTransactions.add(transaction);
mTransactions.addAll(tail);
}
@@ -332,6 +574,16 @@ private boolean checkCorrectGattInstance(BluetoothGatt gatt, String where) {
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 +801,90 @@ public void reset() {
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 0000000000..f5aab72862
--- /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 0000000000..03db77b449
--- /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 0000000000..bf4cad8df3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/ServerTransaction.java
@@ -0,0 +1,76 @@
+/* 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.util.ArrayList;
+import java.util.Collections;
+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 extends AbstractTransaction {
+ private final List mActions = new ArrayList<>(4);
+ private
+ @Nullable
+ GattServerCallback gattCallback;
+
+ public ServerTransaction(String taskName) {
+ super(taskName);
+ }
+
+ public void add(BtLEServerAction action) {
+ mActions.add(action);
+ }
+
+ public List getActions() {
+ return Collections.unmodifiableList(mActions);
+ }
+
+ public boolean isEmpty() {
+ return mActions.isEmpty();
+ }
+
+ @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;
+ }
+
+ @Override
+ public int getActionCount() {
+ return mActions.size();
+ }
+}
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 0000000000..5a8e7693d3
--- /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/Transaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java
index ba91feca72..eef19df950 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 @@
*
* @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 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 GattCallback callback) {
gattCallback = callback;
}
@@ -81,4 +63,9 @@ public void setGattCallback(@Nullable GattCallback callback) {
GattCallback getGattCallback() {
return gattCallback;
}
+
+ @Override
+ public int getActionCount() {
+ return mActions.size();
+ }
}
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 0000000000..817c25f325
--- /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;
+ }
+}
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 3820ab398f..0000000000
--- 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;
- }
- }
-
-}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java
deleted file mode 100644
index 97f3c44010..0000000000
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/* Copyright (C) 2018-2019 Andreas Böhler
- 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.content.Context;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class CasioGATTThread extends Thread {
- CasioGATTServer mServer = null;
- private static final Logger LOG = LoggerFactory.getLogger(CasioGATTThread.class);
- private boolean mStopFlag = false;
- private final Object waitObject = new Object();
-
- public CasioGATTThread(Context context, CasioGB6900DeviceSupport deviceSupport)
- {
- mServer = new CasioGATTServer(context, deviceSupport);
- }
-
- public void setContext(Context ctx) {
- mServer.setContext(ctx);
- }
-
- @Override
- public void run() {
- if (!mServer.initialize()) {
- LOG.error("Error initializing CasioGATTServer. Has the context been set?");
- return;
- }
-
- long waitTime = 60 * 1000;
-
- while (!mStopFlag) {
- synchronized (waitObject) {
- try {
- waitObject.wait(waitTime);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- if (mStopFlag) {
- break;
- }
- }
- mServer.close();
- }
-
- public void quit() {
- mStopFlag = true;
- synchronized (waitObject) {
- waitObject.notify();
- }
- }
-
-
-}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java
index bed8ad0362..18606646e5 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java
@@ -18,11 +18,14 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.net.Uri;
+import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,6 +38,7 @@
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
@@ -48,22 +52,24 @@
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.ServerTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900.operations.InitOperation;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(CasioGB6900DeviceSupport.class);
private ArrayList mCasioCharacteristics = new ArrayList();
- private CasioGATTThread mThread;
private CasioHandlerThread mHandlerThread = null;
private MusicSpec mBufferMusicSpec = null;
private MusicStateSpec mBufferMusicStateSpec = null;
private BluetoothGatt mBtGatt = null;
private CasioGB6900Constants.Model mModel = CasioGB6900Constants.Model.MODEL_CASIO_GENERIC;
- private byte[] mBleSettings = null;
+ private boolean mFirstConnect = false;
- private static final int mCasioSleepTime = 80;
+ private static final int mCasioSleepTime = 50;
public CasioGB6900DeviceSupport() {
super(LOG);
@@ -79,14 +85,30 @@ public CasioGB6900DeviceSupport() {
addSupportedService(CasioGB6900Constants.TX_POWER_SERVICE_UUID);
addSupportedService(CasioGB6900Constants.LINK_LOSS_SERVICE);
addSupportedService(CasioGB6900Constants.IMMEDIATE_ALERT_SERVICE_UUID);
- mThread = new CasioGATTThread(getContext(), this);
+
+ BluetoothGattService casioGATTService = new BluetoothGattService(CasioGB6900Constants.WATCH_CTRL_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+ BluetoothGattCharacteristic bluetoothGATTCharacteristic = new BluetoothGattCharacteristic(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE);
+ bluetoothGATTCharacteristic.setValue(new byte[0]);
+
+ BluetoothGattCharacteristic bluetoothGATTCharacteristic2 = new BluetoothGattCharacteristic(CasioGB6900Constants.NAME_OF_APP_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
+ bluetoothGATTCharacteristic2.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);
+
+ bluetoothGATTCharacteristic2.addDescriptor(bluetoothGattDescriptor);
+
+ casioGATTService.addCharacteristic(bluetoothGATTCharacteristic);
+ casioGATTService.addCharacteristic(bluetoothGATTCharacteristic2);
+
+ addSupportedServerService(casioGATTService);
}
@Override
- public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
- super.setContext(gbDevice, btAdapter, context);
- mThread.setContext(context);
- mThread.start();
+ public boolean connectFirstTime() {
+ GB.toast(getContext(), "After first connect, disable and enable bluetooth on your Casio watch to really connect", Toast.LENGTH_SHORT, GB.INFO);
+ mFirstConnect = true;
+ return super.connect();
}
@Override
@@ -103,12 +125,6 @@ private void close() {
mHandlerThread.interrupt();
mHandlerThread = null;
}
-
- if(mThread != null) {
- mThread.quit();
- mThread.interrupt();
- mThread = null;
- }
}
@Override
@@ -121,6 +137,14 @@ public void onServicesDiscovered(BluetoothGatt gatt) {
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
LOG.info("Initializing");
+ if(mFirstConnect) {
+ gbDevice.setState(GBDevice.State.INITIALIZED);
+ gbDevice.sendDeviceUpdateIntent(getContext());
+ getDevice().setFirmwareVersion("N/A");
+ getDevice().setFirmwareVersion2("N/A");
+ return builder;
+ }
+
String name = gbDevice.getName();
if(name.contains("5600B")) {
@@ -131,17 +155,27 @@ protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
mModel = CasioGB6900Constants.Model.MODEL_CASIO_GENERIC;
}
+ try {
+ new InitOperation(this, builder).perform();
+ } catch (IOException e) {
+ GB.toast(getContext(), "Initializing Casio watch failed", Toast.LENGTH_SHORT, GB.ERROR, e);
+ }
+ /*
gbDevice.setState(GBDevice.State.INITIALIZING);
gbDevice.sendDeviceUpdateIntent(getContext());
+ */
- addCharacteristics();
+ getDevice().setFirmwareVersion("N/A");
+ getDevice().setFirmwareVersion2("N/A");
- builder.setGattCallback(this);
- enableNotifications(builder, true);
+ builder.setGattCallback(this);
configureWatch(builder);
+ addCharacteristics();
+ enableNotifications(builder, true);
+
LOG.info("Initialization Done");
return builder;
@@ -251,52 +285,22 @@ private void writeCasioVirtualServerFeature(TransactionBuilder builder) {
}
}
- private void readBleSettings() {
- try {
- TransactionBuilder builder = performInitialized("readBleSettings");
- builder.read(getCharacteristic(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID));
- builder.queue(getQueue());
- } catch(IOException e) {
- LOG.error("Error reading BLE settings: " + e.getMessage());
- }
- }
-
- private void configureBleSettings() {
- // These values seem to improve connection stability _on my phone_
- // Maybe they should be configurable?
- int slaveLatency = 2;
- int connInterval = 300;
-
- mBleSettings[5] = (byte)(connInterval & 0xff);
- mBleSettings[6] = (byte)((connInterval >> 8) & 0xff);
- mBleSettings[7] = (byte)(slaveLatency & 0xff);
- mBleSettings[8] = (byte)((slaveLatency >> 8) & 0xff);
-
- mBleSettings[9] = 0; // Setting for Disconnect!?
- }
-
- private void writeBleSettings() {
- try {
- TransactionBuilder builder = performInitialized("writeBleSettings");
- builder.write(getCharacteristic(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID), mBleSettings);
- builder.queue(getQueue());
- } catch(IOException e) {
- LOG.error("Error writing BLE settings: " + e.getMessage());
- }
- }
private boolean handleInitResponse(byte data) {
boolean handled = false;
switch(data)
{
case (byte) 1:
LOG.info("Initialization done, setting state to INITIALIZED");
- if(mHandlerThread == null) {
- mHandlerThread = new CasioHandlerThread(getDevice(), getContext(), this);
+ if(mHandlerThread != null) {
+ if(mHandlerThread.isAlive()) {
+ mHandlerThread.quit();
+ mHandlerThread.interrupt();
+ }
}
+ mHandlerThread = new CasioHandlerThread(getDevice(), getContext(), this);
mHandlerThread.start();
gbDevice.setState(GBDevice.State.INITIALIZED);
gbDevice.sendDeviceUpdateIntent(getContext());
- readBleSettings();
handled = true;
break;
default:
@@ -351,7 +355,7 @@ private boolean handleServerFeatureRequests(byte data) {
return true;
}
- private boolean handleCasioCom(byte[] data) {
+ private boolean handleCasioCom(byte[] data, boolean handleTime) {
boolean handled = false;
if(data.length < 3) {
@@ -365,7 +369,11 @@ private boolean handleCasioCom(byte[] data) {
handled = handleInitResponse(data[2]);
break;
case 2:
- handled = handleTimeRequests(data[2]);
+ if(handleTime) {
+ handled = handleTimeRequests(data[2]);
+ } else {
+ handled = true;
+ }
break;
case 7:
handled = handleServerFeatureRequests(data[2]);
@@ -391,63 +399,6 @@ public boolean onCharacteristicRead(BluetoothGatt gatt,
}
LOG.info(str);
}
- else if(characteristicUUID.equals(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID)) {
- mBleSettings = data;
- String str = "Read Casio Setting for BLE: ";
- for(int i=0; i> 6) & 0x03;
- //LOG.info("Call Alert: " + callAlert);
- //int mailAlert = (data[0] >> 2) & 0x03;
- //LOG.info("Mail Alert: " + mailAlert);
- //int snsAlert = (data[2] >> 4) & 0x03;
- //LOG.info("SNS Alert: " + snsAlert);
- //int calAlert = (data[1] >> 6) & 0x03;
- //LOG.info("Calendart Alert: " + calAlert);
- //int otherAlert = (data[0] & 0x03);
- //LOG.info("Other Alert: " + otherAlert);
- //int vibrationValue = (data[3] & 0x0f);
- //LOG.info("Vibration Value: " + vibrationValue);
- //int alarmValue = (data[3] >> 4) & 0x0f;
- // Vibration pattern; A = 0, B = 1, C = 2
- //LOG.info("Alarm Value: " + alarmValue);
- //int animationValue = data[4] & 0x40;
- // Length of Alarm, only 2, 5 and 10 possible
- //LOG.info("Animation Value: " + animationValue);
- // 0 = on
- // 64 = off
- //int useDisableMtuReqBit = data[4] & 0x08;
- // 8 = on
- // 0 = off!?
- //LOG.info("useDisableMtuReqBit: " + useDisableMtuReqBit);
-
- //int slaveLatency = ((data[7] & 0xff) | ((data[8] & 0xff) << 8));
- //int connInterval = ((data[5] & 0xff) | ((data[6] & 0xff) << 8));
- //LOG.info("Slave Latency: " + slaveLatency);
- //LOG.info("Connection Interval: " + connInterval);
- //LOG.info(str);
-
- configureBleSettings();
- writeBleSettings();
- }
else {
return super.onCharacteristicRead(gatt, characteristic, status);
}
@@ -466,11 +417,11 @@ public boolean onCharacteristicChanged(BluetoothGatt gatt,
return true;
if(characteristicUUID.equals(CasioGB6900Constants.CASIO_A_NOT_W_REQ_NOT)) {
- handled = handleCasioCom(data);
+ handled = handleCasioCom(data, true);
}
if(characteristicUUID.equals(CasioGB6900Constants.CASIO_A_NOT_COM_SET_NOT)) {
- handled = handleCasioCom(data);
+ handled = handleCasioCom(data, false);
}
if(characteristicUUID.equals(CasioGB6900Constants.ALERT_LEVEL_CHARACTERISTIC_UUID)) {
@@ -501,6 +452,8 @@ public boolean onCharacteristicChanged(BluetoothGatt gatt,
}
private void showNotification(byte icon, String title, String message) {
+ if(!isConnected())
+ return;
try {
TransactionBuilder builder = performInitialized("showNotification");
int len;
@@ -558,6 +511,10 @@ public void onDeleteNotification(int id) {
public void onSetAlarms(ArrayList extends Alarm> alarms) {
int alarmOffset = 4;
byte[] data = new byte[20];
+
+ if(!isConnected())
+ return;
+
for(int i=0; i alarms) {
@Override
public void onSetTime() {
+ if(!isConnected())
+ return;
+
try {
TransactionBuilder builder = performInitialized("SetTime");
writeCasioLocalTimeInformation(builder);
@@ -622,6 +582,9 @@ public void onSetMusicState(MusicStateSpec stateSpec) {
private void sendMusicInfo()
{
+ if(!isConnected())
+ return;
+
try {
TransactionBuilder builder = performInitialized("sendMusicInfo");
String info = "";
@@ -716,6 +679,9 @@ public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
@Override
public void onFindDevice(boolean start) {
+ if(!isConnected())
+ return;
+
if(start) {
try {
TransactionBuilder builder = performInitialized("findDevice");
@@ -776,4 +742,100 @@ public void onTestNewFunction() {
public void onSendWeather(WeatherSpec weatherSpec) {
}
+
+ @Override
+ public boolean 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 false;
+ }
+
+ LOG.info("will send response to read request from device: " + device.getAddress());
+
+ try {
+ ServerTransactionBuilder builder = performServer("sendNameOfApp");
+ builder.writeServerResponse(device, requestId, 0, offset, CasioGB6900Constants.MUSIC_MESSAGE.getBytes());
+ builder.queue(getQueue());
+ } catch (IOException e) {
+ LOG.warn("sendMusicInfo failed: " + e.getMessage());
+ }
+ return true;
+ }
+
+ 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 boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic,
+ boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+
+ GBDeviceEventMusicControl musicCmd = new GBDeviceEventMusicControl();
+ if (!characteristic.getUuid().equals(CasioGB6900Constants.KEY_CONTAINER_CHARACTERISTIC_UUID)) {
+ LOG.warn("unexpected write request");
+ return false;
+ }
+
+ if((value[0] & 0x03) == 0) {
+ int button = value[1] & 0x0f;
+ LOG.info("Button pressed: " + button);
+ switch(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 false;
+ }
+ evaluateGBDeviceEvent(musicCmd);
+ }
+ else {
+ LOG.info("received from device: " + value.toString());
+ }
+ return true;
+ }
+
+ @Override
+ public boolean useBleScannerForReconnect() {
+ return true;
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioHandlerThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioHandlerThread.java
index d049802c4a..63243602cc 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioHandlerThread.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioHandlerThread.java
@@ -33,7 +33,6 @@ public class CasioHandlerThread extends GBDeviceIoThread {
private boolean mQuit = false;
private CasioGB6900DeviceSupport mDeviceSupport;
private final Object waitObject = new Object();
- //private CasioGATTServer mServer = null;
private int TX_PERIOD = 60;
@@ -43,7 +42,6 @@ public CasioHandlerThread(GBDevice gbDevice, Context context, CasioGB6900DeviceS
super(gbDevice, context);
LOG.info("Initializing Casio Handler Thread");
mQuit = false;
- //mServer = new CasioGATTServer(context, deviceSupport);
mDeviceSupport = deviceSupport;
}
@@ -51,13 +49,6 @@ public CasioHandlerThread(GBDevice gbDevice, Context context, CasioGB6900DeviceS
public void run() {
mQuit = false;
- /*
- if(!mServer.initialize()) {
- LOG.error("Error initializing CasioGATTServer. Has the context been set?");
- return;
- }
- */
-
long waitTime = TX_PERIOD * 1000;
while (!mQuit) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/operations/InitOperation.java
new file mode 100644
index 0000000000..211f3d26e8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/operations/InitOperation.java
@@ -0,0 +1,182 @@
+/* Copyright (C) 2016-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.devices.casiogb6900.operations;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.UUID;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.SecretKeySpec;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900.CasioGB6900DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class InitOperation extends AbstractBTLEOperation {
+ private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class);
+
+ private final TransactionBuilder builder;
+ private byte[] mBleSettings = null;
+
+
+ public InitOperation(CasioGB6900DeviceSupport support, TransactionBuilder builder) {
+ super(support);
+ this.builder = builder;
+ builder.setGattCallback(this);
+ }
+
+ @Override
+ protected void doPerform() throws IOException {
+ builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+ TransactionBuilder builder = getSupport().createTransactionBuilder("readBleSettings");
+ builder.setGattCallback(this);
+ builder.read(getCharacteristic(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID));
+ getSupport().performImmediately(builder);
+ }
+
+ @Override
+ public TransactionBuilder performInitialized(String taskName) throws IOException {
+ throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
+ }
+
+ @Override
+ public boolean onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ UUID characteristicUUID = characteristic.getUuid();
+ LOG.info("Unhandled characteristic changed: " + characteristicUUID);
+ return super.onCharacteristicChanged(gatt, characteristic);
+ }
+
+ private void configureBleSettings() {
+ // These values seem to improve connection stability _on my phone_
+ // Maybe they should be configurable?
+ int slaveLatency = 2;
+ int connInterval = 300;
+
+ mBleSettings[5] = (byte)(connInterval & 0xff);
+ mBleSettings[6] = (byte)((connInterval >> 8) & 0xff);
+ mBleSettings[7] = (byte)(slaveLatency & 0xff);
+ mBleSettings[8] = (byte)((slaveLatency >> 8) & 0xff);
+
+ mBleSettings[9] = 0; // Setting for Disconnect!?
+ }
+
+ private void writeBleSettings() {
+ try {
+ TransactionBuilder builder = getSupport().createTransactionBuilder("writeBleInit");
+ builder.setGattCallback(this);
+ builder.write(getCharacteristic(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID), mBleSettings);
+ getSupport().performImmediately(builder);
+ } catch(IOException e) {
+ LOG.error("Error writing BLE settings: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+
+ UUID characteristicUUID = characteristic.getUuid();
+ byte[] data = characteristic.getValue();
+
+ if(data.length == 0)
+ return true;
+
+ if(characteristicUUID.equals(CasioGB6900Constants.CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID)) {
+ mBleSettings = data;
+ String str = "Read Casio Setting for BLE: ";
+ for(int i=0; i> 6) & 0x03;
+ //LOG.info("Call Alert: " + callAlert);
+ //int mailAlert = (data[0] >> 2) & 0x03;
+ //LOG.info("Mail Alert: " + mailAlert);
+ //int snsAlert = (data[2] >> 4) & 0x03;
+ //LOG.info("SNS Alert: " + snsAlert);
+ //int calAlert = (data[1] >> 6) & 0x03;
+ //LOG.info("Calendart Alert: " + calAlert);
+ //int otherAlert = (data[0] & 0x03);
+ //LOG.info("Other Alert: " + otherAlert);
+ //int vibrationValue = (data[3] & 0x0f);
+ //LOG.info("Vibration Value: " + vibrationValue);
+ //int alarmValue = (data[3] >> 4) & 0x0f;
+ // Vibration pattern; A = 0, B = 1, C = 2
+ //LOG.info("Alarm Value: " + alarmValue);
+ //int animationValue = data[4] & 0x40;
+ // Length of Alarm, only 2, 5 and 10 possible
+ //LOG.info("Animation Value: " + animationValue);
+ // 0 = on
+ // 64 = off
+ //int useDisableMtuReqBit = data[4] & 0x08;
+ // 8 = on
+ // 0 = off!?
+ //LOG.info("useDisableMtuReqBit: " + useDisableMtuReqBit);
+
+ //int slaveLatency = ((data[7] & 0xff) | ((data[8] & 0xff) << 8));
+ //int connInterval = ((data[5] & 0xff) | ((data[6] & 0xff) << 8));
+ //LOG.info("Slave Latency: " + slaveLatency);
+ //LOG.info("Connection Interval: " + connInterval);
+ //LOG.info(str);
+
+ configureBleSettings();
+ writeBleSettings();
+ }
+ else {
+ return super.onCharacteristicRead(gatt, characteristic, status);
+ }
+
+ return true;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java
index 13b8867c5e..36e6b58c6e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java
@@ -24,6 +24,7 @@ public class GBPrefs {
public static final String PACKAGE_BLACKLIST = "package_blacklist";
public static final String PACKAGE_PEBBLEMSG_BLACKLIST = "package_pebblemsg_blacklist";
public static final String CALENDAR_BLACKLIST = "calendar_blacklist";
+ public static final String AUTO_RECONNECT_SCAN = "general_autoreconnectscan";
public static final String AUTO_RECONNECT = "general_autocreconnect";
private static final String AUTO_START = "general_autostartonboot";
public static final String AUTO_EXPORT_ENABLED = "auto_export_enabled";
@@ -35,6 +36,7 @@ public class GBPrefs {
public static final String RTL_SUPPORT = "rtl";
public static final String RTL_CONTEXTUAL_ARABIC = "contextualArabic";
public static boolean AUTO_RECONNECT_DEFAULT = true;
+ public static boolean AUTO_RECONNECT_SCAN_DEFAULT = false;
public static final String USER_NAME = "mi_user_alias";
public static final String USER_NAME_DEFAULT = "gadgetbridge-user";
@@ -53,6 +55,10 @@ public boolean getAutoReconnect() {
return mPrefs.getBoolean(AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT);
}
+ public boolean getAutoReconnectScan() {
+ return mPrefs.getBoolean(AUTO_RECONNECT_SCAN, AUTO_RECONNECT_SCAN_DEFAULT);
+ }
+
public boolean getAutoStart() {
return mPrefs.getBoolean(AUTO_START, AUTO_START_DEFAULT);
}
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 364cccd116..53fcd5e87c 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -644,4 +644,5 @@
Modus-Konfiguration
Konfiguration speichern
Nicht verbunden, Alarm nicht eingestellt.
-
\ No newline at end of file
+ BLE Scanner zur Wiederherstellung der Verbindung verwenden
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2ee0671f9c..b950d136a5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -690,4 +690,5 @@
Mode Configuration
Save Configuration
Not connected, alarm not set.
+ Use BLE Scanner for Reconnect
\ No newline at end of file
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 2b451bf11a..b626a4e683 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -18,6 +18,11 @@
android:defaultValue="false"
android:key="general_autocreconnect"
android:title="@string/pref_title_general_autoreconnect" />
+