diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 90b26bde7..cc014a9dc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -434,6 +434,12 @@
android:name=".devices.pinetime.PineTimeDFUService"
android:label="PineTime Nordic DFU service" />
+
+
+
{
+ GB.toast(GBApplication.getContext().getString(R.string.prompt_restart_gadgetbridge), Toast.LENGTH_LONG, GB.INFO);
+ return true;
+ });
}
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
index 2b254f334..1451d051f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
@@ -431,33 +431,14 @@ public class GBDevice implements Parcelable {
* Set simple to true to get this behavior.
*/
private String getStateString(boolean simple) {
- switch (mState) {
- case NOT_CONNECTED:
- return GBApplication.getContext().getString(R.string.not_connected);
- case WAITING_FOR_RECONNECT:
- return GBApplication.getContext().getString(R.string.waiting_for_reconnect);
- case CONNECTING:
- return GBApplication.getContext().getString(R.string.connecting);
- case CONNECTED:
- if (simple) {
- return GBApplication.getContext().getString(R.string.connecting);
- }
- return GBApplication.getContext().getString(R.string.connected);
- case INITIALIZING:
- if (simple) {
- return GBApplication.getContext().getString(R.string.connecting);
- }
- return GBApplication.getContext().getString(R.string.initializing);
- case AUTHENTICATION_REQUIRED:
- return GBApplication.getContext().getString(R.string.authentication_required);
- case AUTHENTICATING:
- return GBApplication.getContext().getString(R.string.authenticating);
- case INITIALIZED:
- if (simple) {
- return GBApplication.getContext().getString(R.string.connected);
- }
- return GBApplication.getContext().getString(R.string.initialized);
- }
+ try{
+ // TODO: not sure if this is really neccessary...
+ if(simple){
+ return GBApplication.getContext().getString(mState.getSimpleStringId());
+ }
+ return GBApplication.getContext().getString(mState.getStringId());
+ }catch (Exception e){}
+
return GBApplication.getContext().getString(R.string.unknown_state);
}
@@ -744,20 +725,40 @@ public class GBDevice implements Parcelable {
}
public enum State {
- // Note: the order is important!
- NOT_CONNECTED,
- WAITING_FOR_RECONNECT,
- CONNECTING,
- CONNECTED,
- INITIALIZING,
- AUTHENTICATION_REQUIRED, // some kind of pairing is required by the device
- AUTHENTICATING, // some kind of pairing is requested by the device
+ NOT_CONNECTED(R.string.not_connected),
+ WAITING_FOR_RECONNECT(R.string.waiting_for_reconnect),
+ WAITING_FOR_SCAN(R.string.device_state_waiting_scan),
+ CONNECTING(R.string.connecting),
+ CONNECTED(R.string.connected, R.string.connecting),
+ INITIALIZING(R.string.initializing, R.string.connecting),
+ AUTHENTICATION_REQUIRED(R.string.authentication_required), // some kind of pairing is required by the device
+ AUTHENTICATING(R.string.authenticating), // some kind of pairing is requested by the device
/**
* Means that the device is connected AND all the necessary initialization steps
* have been performed. At the very least, this means that basic information like
* device name, firmware version, hardware revision (as applicable) is available
* in the GBDevice.
*/
- INITIALIZED,
+ INITIALIZED(R.string.initialized, R.string.connected);
+
+
+ private int stringId, simpleStringId;
+
+ State(int stringId, int simpleStringId) {
+ this.stringId = stringId;
+ this.simpleStringId = simpleStringId;
+ }
+
+ State(int stringId) {
+ this(stringId, stringId);
+ }
+
+ public int getStringId() {
+ return stringId;
+ }
+
+ public int getSimpleStringId() {
+ return simpleStringId;
+ }
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
index f701ce681..51d00f795 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
@@ -127,7 +127,7 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
protected GBDevice gbDevice;
private BluetoothAdapter btAdapter;
private Context context;
- private boolean autoReconnect;
+ private boolean autoReconnect, scanReconnect;
@@ -171,6 +171,16 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
return autoReconnect;
}
+ @Override
+ public void setScanReconnect(boolean scanReconnect) {
+ this.scanReconnect = scanReconnect;
+ }
+
+ @Override
+ public boolean getScanReconnect(){
+ return this.scanReconnect;
+ }
+
@Override
public boolean getImplicitCallbackModify() {
return true;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
index ce9f0a0ec..c0d7cdbf2 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
@@ -97,6 +97,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLEScanService;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.AutoConnectIntervalReceiver;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBAutoFetchReceiver;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
@@ -226,8 +227,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
@SuppressLint("StaticFieldLeak") // only used for test cases
private static DeviceSupportFactory DEVICE_SUPPORT_FACTORY = null;
- private boolean mStarted = false;
-
private DeviceSupportFactory mFactory;
private final ArrayList deviceStructs = new ArrayList<>(1);
private final HashMap> cachedNotifications = new HashMap<>();
@@ -273,6 +272,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
private final String ACTION_DEVICE_CONNECTED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECTED";
private final int NOTIFICATIONS_CACHE_MAX = 10; // maximum amount of notifications to cache per device while disconnected
private boolean allowBluetoothIntentApi = false;
+ private boolean reconnectViaScan = GBPrefs.RECONNECT_SCAN_DEFAULT;
private void sendDeviceConnectedBroadcast(String address){
if(!allowBluetoothIntentApi){
@@ -369,6 +369,20 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
sendDeviceConnectedBroadcast(device.getAddress());
sendCachedNotifications(device);
}
+ }else if(BLEScanService.EVENT_DEVICE_FOUND.equals(action)){
+ String deviceAddress = intent.getStringExtra(BLEScanService.EXTRA_DEVICE_ADDRESS);
+
+ GBDevice target = GBApplication
+ .app()
+ .getDeviceManager()
+ .getDeviceByAddress(deviceAddress);
+
+ if(target == null){
+ LOG.error("onReceive: device not found");
+ return;
+ }
+
+ connectToDevice(target);
}
}
};
@@ -400,24 +414,20 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
setReceiversEnableState(enableReceivers, anyDeviceInitialized, features, devicesWithCalendar);
}
- @Override
- public void onCreate() {
- LOG.debug("DeviceCommunicationService is being created");
- super.onCreate();
- LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
- mFactory = getDeviceSupportFactory();
+ private void registerInternalReceivers(){
+ IntentFilter localFilter = new IntentFilter();
+ localFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+ localFilter.addAction(BLEScanService.EVENT_DEVICE_FOUND);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, localFilter);
+ }
+ private void registerExternalReceivers(){
mBlueToothConnectReceiver = new BluetoothConnectReceiver(this);
registerReceiver(mBlueToothConnectReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
mAutoConnectInvervalReceiver= new AutoConnectIntervalReceiver(this);
registerReceiver(mAutoConnectInvervalReceiver, new IntentFilter("GB_RECONNECT"));
- if (hasPrefs()) {
- getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this);
- allowBluetoothIntentApi = getPrefs().getBoolean(GBPrefs.PREF_ALLOW_INTENT_API, false);
- }
-
IntentFilter bluetoothCommandFilter = new IntentFilter();
bluetoothCommandFilter.addAction(COMMAND_BLUETOOTH_CONNECT);
registerReceiver(bluetoothCommandReceiver, bluetoothCommandFilter);
@@ -429,6 +439,46 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
registerReceiver(intentApiReceiver, intentApiReceiver.buildFilter());
}
+ @Override
+ public void onCreate() {
+ LOG.debug("DeviceCommunicationService is being created");
+ super.onCreate();
+ mFactory = getDeviceSupportFactory();
+
+ registerInternalReceivers();
+ registerExternalReceivers();
+
+ if (hasPrefs()) {
+ getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this);
+ allowBluetoothIntentApi = getPrefs().getBoolean(GBPrefs.PREF_ALLOW_INTENT_API, false);
+ reconnectViaScan = getGBPrefs().getAutoReconnectByScan();
+ }
+
+ startForeground();
+ if(reconnectViaScan) {
+ scanAllDevices();
+
+ Intent scanServiceIntent = new Intent(this, BLEScanService.class);
+ startService(scanServiceIntent);
+ }
+ }
+
+ private void scanAllDevices(){
+ List devices = GBApplication.app().getDeviceManager().getDevices();
+ for(GBDevice device : devices){
+ if(device.getState() != GBDevice.State.NOT_CONNECTED){
+ continue;
+ }
+ boolean shouldAutoConnect = getGBPrefs().getAutoReconnect(device);
+ if(!shouldAutoConnect){
+ continue;
+ }
+ createDeviceStruct(device);
+ device.setState(GBDevice.State.WAITING_FOR_SCAN);
+ device.sendDeviceUpdateIntent(this);
+ }
+ }
+
private DeviceSupportFactory getDeviceSupportFactory() {
if (DEVICE_SUPPORT_FACTORY != null) {
return DEVICE_SUPPORT_FACTORY;
@@ -436,144 +486,136 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
return new DeviceSupportFactory(this);
}
+ private void createDeviceStruct(GBDevice target){
+ DeviceStruct registeredStruct = new DeviceStruct();
+ registeredStruct.setDevice(target);
+ registeredStruct.setCoordinator(target.getDeviceCoordinator());
+ deviceStructs.add(registeredStruct);
+ }
+
+ private void connectToDevice(GBDevice device){
+ connectToDevice(device, false);
+ }
+
+ private void connectToDevice(GBDevice device, boolean firstTime){
+ List gbDevs = null;
+ boolean fromExtra = false;
+
+ Prefs prefs = getPrefs();
+
+ if (device != null) {
+ gbDevs = new ArrayList<>();
+ gbDevs.add(device);
+ fromExtra = true;
+ } else if (prefs.getBoolean(GBPrefs.RECONNECT_ONLY_TO_CONNECTED, true)) {
+ List gbAllDevs = GBApplication.app().getDeviceManager().getDevices();
+ Set lastDeviceAddresses = prefs.getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet());
+ if (gbAllDevs != null && !gbAllDevs.isEmpty() && !lastDeviceAddresses.isEmpty()) {
+ gbDevs = new ArrayList<>();
+ for(GBDevice gbDev : gbAllDevs) {
+ if (lastDeviceAddresses.contains(gbDev.getAddress())) {
+ gbDevs.add(gbDev);
+ }
+ }
+ }
+ } else {
+ gbDevs = GBApplication.app().getDeviceManager().getDevices();
+ }
+
+ if(gbDevs == null || gbDevs.size() == 0) {
+ return;
+ }
+
+ for(GBDevice gbDevice : gbDevs) {
+ String btDeviceAddress = gbDevice.getAddress();
+
+ boolean autoReconnect = GBPrefs.AUTO_RECONNECT_DEFAULT;
+ if (prefs != null && prefs.getPreferences() != null) {
+ autoReconnect = getGBPrefs().getAutoReconnect(gbDevice);
+ if(!fromExtra && !autoReconnect) {
+ continue;
+ }
+ Set lastDeviceAddresses = prefs.getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet());
+ if (!lastDeviceAddresses.contains(btDeviceAddress)) {
+ lastDeviceAddresses = new HashSet(lastDeviceAddresses);
+ lastDeviceAddresses.add(btDeviceAddress);
+ prefs.getPreferences().edit().putStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, lastDeviceAddresses).apply();
+ }
+ }
+
+ if(!fromExtra && !autoReconnect) {
+ continue;
+ }
+
+ DeviceStruct registeredStruct = getDeviceStructOrNull(gbDevice);
+ if(registeredStruct != null){
+ boolean deviceAlreadyConnected = isDeviceConnecting(registeredStruct.getDevice()) || isDeviceConnected(registeredStruct.getDevice());
+ if(deviceAlreadyConnected){
+ break;
+ }
+ try {
+ removeDeviceSupport(gbDevice);
+ } catch (DeviceNotFoundException e) {
+ e.printStackTrace();
+ }
+ }else{
+ createDeviceStruct(gbDevice);
+ }
+
+ try {
+ DeviceSupport deviceSupport = mFactory.createDeviceSupport(gbDevice);
+ if (deviceSupport != null) {
+ setDeviceSupport(gbDevice, deviceSupport);
+ if (firstTime) {
+ deviceSupport.connectFirstTime();
+ } else {
+ deviceSupport.setAutoReconnect(autoReconnect);
+ deviceSupport.setScanReconnect(reconnectViaScan);
+ deviceSupport.connect();
+ }
+ } else {
+ GB.toast(this, getString(R.string.cannot_connect, "Can't create device support"), Toast.LENGTH_SHORT, GB.ERROR);
+ }
+ } catch (Exception e) {
+ GB.toast(this, getString(R.string.cannot_connect, e.getMessage()), Toast.LENGTH_SHORT, GB.ERROR, e);
+ }
+
+ for(DeviceStruct struct2 : deviceStructs){
+ struct2.getDevice().sendDeviceUpdateIntent(this);
+ }
+ }
+ }
+
@Override
public synchronized int onStartCommand(Intent intent, int flags, int startId) {
-
if (intent == null) {
LOG.info("no intent");
- return START_NOT_STICKY;
+ return START_STICKY;
}
String action = intent.getAction();
- boolean firstTime = intent.getBooleanExtra(EXTRA_CONNECT_FIRST_TIME, false);
if (action == null) {
LOG.info("no action");
- return START_NOT_STICKY;
+ return START_STICKY;
}
LOG.debug("Service startcommand: " + action);
- if (!action.equals(ACTION_START) && !action.equals(ACTION_CONNECT)) {
- if (!mStarted) {
- // using the service before issuing ACTION_START
- LOG.info("Must start service with " + ACTION_START + " or " + ACTION_CONNECT + " before using it: " + action);
- return START_NOT_STICKY;
- }
-
- // TODO
- /*if (mDeviceSupport == null || (!isInitialized() && !action.equals(ACTION_DISCONNECT) && (!mDeviceSupport.useAutoConnect() || isConnected()))) {
- // trying to send notification without valid Bluetooth connection
- if (mGBDevice != null) {
- // at least send back the current device state
- mGBDevice.sendDeviceUpdateIntent(this);
- }
- return START_STICKY;
- }*/
- }
-
// when we get past this, we should have valid mDeviceSupport and mGBDevice instances
+ GBDevice targetDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
Prefs prefs = getPrefs();
switch (action) {
- case ACTION_START:
- start();
- break;
case ACTION_CONNECT:
- start(); // ensure started
- List gbDevs = null;
- boolean fromExtra = false;
-
- GBDevice extraDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
- if (extraDevice != null) {
- gbDevs = new ArrayList<>();
- gbDevs.add(extraDevice);
- fromExtra = true;
- } else if (prefs.getBoolean(GBPrefs.RECONNECT_ONLY_TO_CONNECTED, true)) {
- List gbAllDevs = GBApplication.app().getDeviceManager().getDevices();
- Set lastDeviceAddresses = prefs.getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet());
- if (gbAllDevs != null && !gbAllDevs.isEmpty() && !lastDeviceAddresses.isEmpty()) {
- gbDevs = new ArrayList<>();
- for(GBDevice gbDev : gbAllDevs) {
- if (lastDeviceAddresses.contains(gbDev.getAddress())) {
- gbDevs.add(gbDev);
- }
- }
- }
- } else {
- gbDevs = GBApplication.app().getDeviceManager().getDevices();
- }
-
- if(gbDevs == null || gbDevs.size() == 0) {
- return START_NOT_STICKY;
- }
-
- for(GBDevice gbDevice : gbDevs) {
- String btDeviceAddress = gbDevice.getAddress();
-
- boolean autoReconnect = GBPrefs.AUTO_RECONNECT_DEFAULT;
- if (prefs != null && prefs.getPreferences() != null) {
- autoReconnect = getGBPrefs().getAutoReconnect(gbDevice);
- if(!fromExtra && !autoReconnect) {
- continue;
- }
- Set lastDeviceAddresses = prefs.getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet());
- if (!lastDeviceAddresses.contains(btDeviceAddress)) {
- lastDeviceAddresses = new HashSet(lastDeviceAddresses);
- lastDeviceAddresses.add(btDeviceAddress);
- prefs.getPreferences().edit().putStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, lastDeviceAddresses).apply();
- }
- }
-
- if(!fromExtra && !autoReconnect) {
- continue;
- }
-
- DeviceStruct registeredStruct = getDeviceStructOrNull(gbDevice);
- if(registeredStruct != null){
- boolean deviceAlreadyConnected = isDeviceConnecting(registeredStruct.getDevice()) || isDeviceConnected(registeredStruct.getDevice());
- if(deviceAlreadyConnected){
- break;
- }
- try {
- removeDeviceSupport(gbDevice);
- } catch (DeviceNotFoundException e) {
- e.printStackTrace();
- }
- }else{
- registeredStruct = new DeviceStruct();
- registeredStruct.setDevice(gbDevice);
- registeredStruct.setCoordinator(gbDevice.getDeviceCoordinator());
- deviceStructs.add(registeredStruct);
- }
-
- try {
- DeviceSupport deviceSupport = mFactory.createDeviceSupport(gbDevice);
- if (deviceSupport != null) {
- setDeviceSupport(gbDevice, deviceSupport);
- if (firstTime) {
- deviceSupport.connectFirstTime();
- } else {
- deviceSupport.setAutoReconnect(autoReconnect);
- deviceSupport.connect();
- }
- } else {
- GB.toast(this, getString(R.string.cannot_connect, "Can't create device support"), Toast.LENGTH_SHORT, GB.ERROR);
- }
- } catch (Exception e) {
- GB.toast(this, getString(R.string.cannot_connect, e.getMessage()), Toast.LENGTH_SHORT, GB.ERROR, e);
- }
-
- for(DeviceStruct struct2 : deviceStructs){
- struct2.getDevice().sendDeviceUpdateIntent(this);
- }
- }
+ boolean firstTime = intent.getBooleanExtra(EXTRA_CONNECT_FIRST_TIME, false);
+ connectToDevice(targetDevice, firstTime);
break;
default:
- GBDevice targetedDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
ArrayList targetedDevices = new ArrayList<>();
- if(targetedDevice != null){
- targetedDevices.add(targetedDevice);
+ if(targetDevice != null){
+ targetedDevices.add(targetDevice);
}else{
for(GBDevice device : getGBDevices()){
if(isDeviceInitialized(device)){
@@ -1042,16 +1084,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
throw new DeviceNotFoundException(device);
}
- private void start() {
- if (!mStarted) {
- GB.createNotificationChannels(this);
- startForeground(GB.NOTIFICATION_ID, GB.createNotification(getString(R.string.gadgetbridge_running), this));
- mStarted = true;
- }
- }
-
- public boolean isStarted() {
- return mStarted;
+ private void startForeground() {
+ GB.createNotificationChannels(this);
+ startForeground(GB.NOTIFICATION_ID, GB.createNotification(getString(R.string.gadgetbridge_running), this));
}
private boolean isDeviceConnected(GBDevice device) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java
index 95b5d06d6..a27165535 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java
@@ -114,6 +114,10 @@ public interface DeviceSupport extends EventHandler {
*/
boolean getAutoReconnect();
+ void setScanReconnect(boolean enable);
+
+ boolean getScanReconnect();
+
/**
* Returns whether the gatt callback should be implicitly set to the one on the transaction,
* even if it was not set directly on the transaction. If true, the gatt callback will always
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
index 70effe0c3..78c17c0ef 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
@@ -99,6 +99,16 @@ public class ServiceDeviceSupport implements DeviceSupport {
return delegate.getAutoReconnect();
}
+ @Override
+ public void setScanReconnect(boolean enable) {
+ delegate.setScanReconnect(enable);
+ }
+
+ @Override
+ public boolean getScanReconnect(){
+ return delegate.getScanReconnect();
+ }
+
@Override
public boolean getImplicitCallbackModify() {
return delegate.getImplicitCallbackModify();
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 7ecd3ce26..834b65fd4 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
@@ -78,6 +78,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
if (mQueue == null) {
mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, this, getContext(), mSupportedServerServices);
mQueue.setAutoReconnect(getAutoReconnect());
+ mQueue.setScanReconnect(getScanReconnect());
mQueue.setImplicitGattCallbackModify(getImplicitCallbackModify());
mQueue.setSendWriteRequestResponse(getSendWriteRequestResponse());
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLEScanService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLEScanService.java
new file mode 100644
index 000000000..db3823adb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLEScanService.java
@@ -0,0 +1,407 @@
+package nodomain.freeyourgadget.gadgetbridge.service.btle;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.IBinder;
+import android.widget.RemoteViews;
+
+import androidx.core.app.NotificationCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class BLEScanService extends Service {
+ public static final String COMMAND_SCAN_DEVICE = "nodomain.freeyourgadget.gadgetbridge.service.ble.scan.command.START_SCAN_FOR_DEVICE";
+ public static final String COMMAND_START_SCAN_ALL = "nodomain.freeyourgadget.gadgetbridge.service.ble.scan.command.START_SCAN_ALL";
+ public static final String COMMAND_STOP_SCAN_ALL = "nodomain.freeyourgadget.gadgetbridge.service.ble.scan.command.STOP_SCAN_ALL";
+
+ public static final String EVENT_DEVICE_FOUND = "nodomain.freeyourgadget.gadgetbridge.service.ble.scan.event.DEVICE_FOUND";
+
+ public static final String EXTRA_DEVICE = "EXTRA_DEVICE";
+ public static final String EXTRA_DEVICE_ADDRESS = "EXTRA_DEVICE_ADDRESS";
+
+ // 5 minutes scan restart interval
+ private final int DELAY_SCAN_RESTART = 5 * 60 * 1000;
+
+ private LocalBroadcastManager localBroadcastManager;
+ private NotificationManager notificationManager;
+ private BluetoothManager bluetoothManager;
+ private BluetoothLeScanner scanner;
+
+ private Logger LOG = LoggerFactory.getLogger(getClass());
+ // private final ArrayList currentFilters = new ArrayList<>();
+
+ private enum ScanningState {
+ NOT_SCANNING,
+ SCANNING_WITHOUT_FILTERS,
+ SCANNING_WITH_FILTERS;
+
+ public boolean isDoingAnyScan(){
+ return ordinal() > NOT_SCANNING.ordinal();
+ }
+
+ public boolean shouldDiscardAfterFirstMatch(){
+ return this == SCANNING_WITH_FILTERS;
+ }
+ };
+ private ScanningState currentState = ScanningState.NOT_SCANNING;
+
+ private final ScanCallback scanCallback = new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ super.onScanResult(callbackType, result);
+ BluetoothDevice device = result.getDevice();
+
+ LOG.debug("onScanResult: " + result);
+
+ Intent intent = new Intent(EVENT_DEVICE_FOUND);
+ intent.putExtra(EXTRA_DEVICE_ADDRESS, device.getAddress());
+ localBroadcastManager.sendBroadcast(intent);
+
+ // device found, attempt connection
+ // stop scanning for device for now
+ // will restart when connection attempt fails
+ if(currentState.shouldDiscardAfterFirstMatch()) {
+ // stopScanningForDevice(device.getAddress());
+ }
+ }
+
+ @Override
+ public void onScanFailed(int errorCode) {
+ super.onScanFailed(errorCode);
+
+ LOG.error("onScanFailed: " + errorCode);
+
+ updateNotification("Scan failed: " + errorCode);
+ }
+ };
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
+ scanner = bluetoothManager.getAdapter().getBluetoothLeScanner();
+
+ localBroadcastManager = LocalBroadcastManager.getInstance(this);
+
+ notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+
+ registerReceivers();
+
+ this.startForeground();
+
+ if(scanner == null){
+ updateNotification("Waiting for bluetooth...");
+ }else{
+ restartScan(true);
+ }
+
+ // schedule after 5 seconds to fix weird timing of both services
+ scheduleRestartScan(5000);
+ }
+
+ private void scheduleRestartScan(){
+ scheduleRestartScan(DELAY_SCAN_RESTART);
+ }
+
+ private void scheduleRestartScan(long millis){
+ Handler handler = new Handler();
+ handler.postDelayed(() -> {
+ LOG.debug("restarting scan...");
+ try {
+ restartScan(true);
+ }catch (Exception e){
+ LOG.error("error during scheduled scan restart", e);
+ }
+ scheduleRestartScan();
+ }, millis);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ unregisterReceivers();
+ }
+
+ private void updateNotification(boolean isScanning, int scannedDeviceCount){
+ notificationManager.notify(
+ GB.NOTIFICATION_ID_SCAN,
+ createNotification(isScanning, scannedDeviceCount)
+ );
+ }
+
+ private void updateNotification(String content){
+ notificationManager.notify(
+ GB.NOTIFICATION_ID_SCAN,
+ createNotification(content, R.drawable.ic_bluetooth)
+ );
+ }
+
+ private Notification createNotification(boolean isScanning, int scannedDevicesCount){
+ int icon = R.drawable.ic_bluetooth;
+ String content = "Not scanning";
+ if(isScanning){
+ icon = R.drawable.ic_bluetooth_searching;
+ if(scannedDevicesCount == 1) {
+ content = String.format("Scanning %d device", scannedDevicesCount);
+ }else if(scannedDevicesCount > 1){
+ content = String.format("Scanning %d devices", scannedDevicesCount);
+ }else{
+ content = "Scanning all devices";
+ }
+ }
+
+ return createNotification(content, icon);
+ }
+
+ private Notification createNotification(String content, int icon){
+
+ return new NotificationCompat
+ .Builder(this, GB.NOTIFICATION_CHANNEL_ID)
+ .setContentTitle("Scan service")
+ .setContentText(content)
+ .setSmallIcon(icon)
+ .build();
+ }
+
+ private void startForeground(){
+ Notification serviceNotification = createNotification(false, 0);
+
+ super.startForeground(GB.NOTIFICATION_ID_SCAN, serviceNotification);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if(intent == null){
+ return START_STICKY;
+ }
+ String action = intent.getAction();
+ if(action == null){
+ return START_STICKY;
+ }
+ switch (action) {
+ case COMMAND_SCAN_DEVICE:
+ handleScanDevice(intent);
+ break;
+ case COMMAND_START_SCAN_ALL:
+ handleScanAll(intent);
+ break;
+ case COMMAND_STOP_SCAN_ALL:
+ handleStopScanAll(intent);
+ break;
+ default:
+ return START_STICKY;
+ }
+ return START_STICKY;
+ }
+
+ private void handleStopScanAll(Intent intent){
+ restartScan(true);
+ }
+
+ private void handleScanAll(Intent intent){
+ if(currentState != ScanningState.SCANNING_WITHOUT_FILTERS){
+ restartScan(false);
+ }
+ }
+
+ private void handleScanDevice(Intent intent){
+ /*
+ GBDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
+ if(device == null){
+ return;
+ }
+ scanForDevice(device);
+ */
+ restartScan(true);
+ }
+
+
+ /*private boolean isDeviceIncludedInCurrentFilters(GBDevice device){
+ for(ScanFilter currentFilter : currentFilters){
+ if(device.getAddress().equals(currentFilter.getDeviceAddress())){
+ return true;
+ }
+ }
+ return false;
+ }
+ */
+
+ /*
+ private void stopScanningForDevice(GBDevice device){
+ this.stopScanningForDevice(device.getAddress());
+ }
+ */
+
+ /*
+ private void stopScanningForDevice(String deviceAddress){
+ currentFilters.removeIf(scanFilter -> scanFilter
+ .getDeviceAddress()
+ .equals(deviceAddress)
+ );
+
+ restartScan(true);
+ }
+ */
+
+ /*
+ private void scanForDevice(GBDevice device){
+ if(isDeviceIncludedInCurrentFilters(device)){
+ // already scanning for device
+ return;
+ }
+ ScanFilter deviceFilter = new ScanFilter.Builder()
+ .setDeviceAddress(device.getAddress())
+ .build();
+
+ currentFilters.add(deviceFilter);
+
+ // restart scan here
+ restartScan(true);
+ }
+ */
+
+ BroadcastReceiver deviceStateUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ GBDevice.DeviceUpdateSubject subject =
+ (GBDevice.DeviceUpdateSubject)
+ intent.getSerializableExtra(GBDevice.EXTRA_UPDATE_SUBJECT);
+
+ if(subject != GBDevice.DeviceUpdateSubject.CONNECTION_STATE){
+ return;
+ }
+ restartScan(true);
+ }
+ };
+
+ BroadcastReceiver bluetoothStateChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if(intent == null){
+ return;
+ }
+ final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+ switch(state) {
+ case BluetoothAdapter.STATE_OFF:
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ updateNotification("Waiting for bluetooth...");
+ break;
+ case BluetoothAdapter.STATE_ON:
+ restartScan(true);
+ break;
+ }
+ }
+ };
+
+ private void registerReceivers(){
+ localBroadcastManager.registerReceiver(
+ deviceStateUpdateReceiver,
+ new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED)
+ );
+
+ registerReceiver(
+ bluetoothStateChangedReceiver,
+ new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
+ );
+ }
+
+ private void unregisterReceivers(){
+ localBroadcastManager.unregisterReceiver(deviceStateUpdateReceiver);
+
+ unregisterReceiver(bluetoothStateChangedReceiver);
+ }
+
+ private void restartScan(boolean applyFilters){
+ if(scanner == null){
+ scanner = bluetoothManager.getAdapter().getBluetoothLeScanner();
+ }
+ if(scanner == null){
+ // at this point we should already be waiting for bluetooth to turn back on
+ LOG.debug("cannot enable scan since bluetooth seems off (scanner == null)");
+ return;
+ }
+ if(bluetoothManager.getAdapter().getState() != BluetoothAdapter.STATE_ON){
+ // again, we should be waiting for the adapter to turn on again
+ LOG.debug("Bluetooth adapter state off");
+ return;
+ }
+ if(currentState.isDoingAnyScan()){
+ scanner.stopScan(scanCallback);
+ }
+ ArrayList scanFilters = null;
+
+ if(applyFilters) {
+ List devices = GBApplication.app().getDeviceManager().getDevices();
+
+ scanFilters = new ArrayList<>(devices.size());
+
+ for (GBDevice device : devices) {
+ if (device.getState() == GBDevice.State.WAITING_FOR_SCAN) {
+ scanFilters.add(new ScanFilter.Builder()
+ .setDeviceAddress(device.getAddress())
+ .build()
+ );
+ }
+ }
+
+ if(scanFilters.size() == 0){
+ // no need to start scanning
+ LOG.debug("restartScan: stopping BLE scan, no devices");
+ currentState = ScanningState.NOT_SCANNING;
+ updateNotification(false, 0);
+ return;
+ }
+ }
+
+ ScanSettings scanSettings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) // enforced anyway in background
+ .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
+ .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
+ .setLegacy(false)
+ .build();
+
+ scanner.startScan(scanFilters, scanSettings, scanCallback);
+ if(applyFilters) {
+ LOG.debug("restartScan: started scan for " + scanFilters.size() + " devices");
+ updateNotification(true, scanFilters.size());
+ currentState = ScanningState.SCANNING_WITH_FILTERS;
+ }else{
+ LOG.debug("restartScan: started scan for all devices");
+ updateNotification(true, 0);
+ currentState = ScanningState.SCANNING_WITHOUT_FILTERS;
+ }
+
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // TODO: Return the communication channel to the service.
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+}
\ No newline at end of file
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 83bdb5695..8654086cc 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
@@ -81,6 +81,7 @@ public final class BtLEQueue {
private final InternalGattCallback internalGattCallback;
private final InternalGattServerCallback internalGattServerCallback;
private boolean mAutoReconnect;
+ private boolean scanReconnect;
private boolean mImplicitGattCallbackModify = true;
private boolean mSendWriteRequestResponse = false;
@@ -218,6 +219,10 @@ public final class BtLEQueue {
mAutoReconnect = enable;
}
+ public void setScanReconnect(boolean enable){
+ this.scanReconnect = enable;
+ }
+
public void setImplicitGattCallbackModify(final boolean enable) {
mImplicitGattCallbackModify = enable;
}
@@ -355,6 +360,12 @@ public final class BtLEQueue {
*/
private boolean maybeReconnect() {
if (mAutoReconnect && mBluetoothGatt != null) {
+ if(scanReconnect){
+ LOG.info("Waiting for BLE scan before attempting reconnection...");
+ setDeviceConnectionState(State.WAITING_FOR_SCAN);
+ return true;
+ }
+
LOG.info("Enabling automatic ble reconnect...");
boolean result = mBluetoothGatt.connect();
mPauseTransaction = false;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
index f711c58a9..7afd5aa42 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
@@ -79,6 +79,7 @@ public class GB {
public static final int NOTIFICATION_ID_EXPORT_FAILED = 5;
public static final int NOTIFICATION_ID_PHONE_FIND = 6;
public static final int NOTIFICATION_ID_GPS = 7;
+ public static final int NOTIFICATION_ID_SCAN = 8;
public static final int NOTIFICATION_ID_ERROR = 42;
private static final Logger LOG = LoggerFactory.getLogger(GB.class);
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 c1799e886..7294cf9ee 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java
@@ -59,6 +59,9 @@ public class GBPrefs {
public static boolean AUTO_RECONNECT_DEFAULT = true;
public static final String PREF_ALLOW_INTENT_API = "prefs_key_allow_bluetooth_intent_api";
+ public static final String RECONNECT_SCAN_KEY = "prefs_general_key_auto_reconnect_scan";
+ public static final boolean RECONNECT_SCAN_DEFAULT = false;
+
public static final String USER_NAME = "mi_user_alias";
public static final String USER_NAME_DEFAULT = "gadgetbridge-user";
private static final String USER_BIRTHDAY = "";
@@ -80,6 +83,10 @@ public class GBPrefs {
return deviceSpecificPreferences.getBoolean(DEVICE_AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT);
}
+ public boolean getAutoReconnectByScan() {
+ return mPrefs.getBoolean(RECONNECT_SCAN_KEY, RECONNECT_SCAN_DEFAULT);
+ }
+
public boolean getAutoStart() {
return mPrefs.getBoolean(AUTO_START, AUTO_START_DEFAULT);
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0a1455b53..231d862c5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2684,4 +2684,8 @@
Could not post ongoing notification due to missing permission
Features
Enabled features for this test device
+ Waiting for device scan
+ Reconnect by BLE scan
+ Wait for device scan instead of blind connection attempts
+ Please restart GB in order to take effect.
diff --git a/app/src/main/res/xml/discovery_pairing_preferences.xml b/app/src/main/res/xml/discovery_pairing_preferences.xml
index c21b4f7a5..9c427ae3d 100644
--- a/app/src/main/res/xml/discovery_pairing_preferences.xml
+++ b/app/src/main/res/xml/discovery_pairing_preferences.xml
@@ -23,4 +23,11 @@
android:summary="@string/discover_unsupported_devices_description"
android:title="@string/discover_unsupported_devices"
app:iconSpaceReserved="false" />
+