diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java index 97b61dda0..9669a2cea 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java @@ -586,7 +586,7 @@ public class ExportImportTest { Mockito.when(barometer.getValue()).thenReturn(new AltitudeGainLoss(altitudeGain, altitudeGain)); sensorDataSet.add(barometer); } else { - sensorDataSet.add(new AggregatorBarometer("")); + sensorDataSet.add(new AggregatorBarometer("test", null)); } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorBarometerTest.java b/src/androidTest/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorBarometerTest.java index 5ef2b51c6..88db170fc 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorBarometerTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorBarometerTest.java @@ -16,7 +16,7 @@ public class AggregatorBarometerTest { @Test public void getAltitudeGainLoss_downhill() { // given - AggregatorBarometer subject = new AggregatorBarometer(""); + AggregatorBarometer subject = new AggregatorBarometer("test", null); // then addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f}); diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothConnectionManager.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothConnectionManager.java index 2c0cf74f0..c3aabdc95 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothConnectionManager.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothConnectionManager.java @@ -17,6 +17,7 @@ package de.dennisguse.opentracks.sensors; import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; @@ -33,19 +34,22 @@ import androidx.annotation.NonNull; import java.util.Optional; import java.util.UUID; +import de.dennisguse.opentracks.sensors.driver.Driver; import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface; /** * Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications. */ @SuppressLint("MissingPermission") -public class BluetoothConnectionManager { +public class BluetoothConnectionManager implements Driver { private static final String TAG = BluetoothConnectionManager.class.getSimpleName(); private final SensorManager.SensorDataChangedObserver observer; private final SensorHandlerInterface sensorHandler; + + private final BluetoothAdapter bluetoothAdapter; private BluetoothGatt bluetoothGatt; private final BluetoothGattCallback connectCallback = new BluetoothGattCallback() { @Override @@ -101,9 +105,12 @@ public class BluetoothConnectionManager { return; } - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(descriptor); - + if (!descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) { + Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID could not be set to ENABLE_NOTIFICATION_VALUE"); + } + if (!gatt.writeDescriptor(descriptor)) { + Log.e(TAG, "CLIENT_CHARACTERISTIC_CONFIG_UUID descriptor could not be written"); + } } @Override @@ -124,17 +131,46 @@ public class BluetoothConnectionManager { } }; - BluetoothConnectionManager(SensorManager.SensorDataChangedObserver observer, SensorHandlerInterface sensorHandler) { + BluetoothConnectionManager(BluetoothAdapter bluetoothAdapter, SensorManager.SensorDataChangedObserver observer, SensorHandlerInterface sensorHandler) { + this.bluetoothAdapter = bluetoothAdapter; this.observer = observer; this.sensorHandler = sensorHandler; } - synchronized void connect(Context context, Handler handler, @NonNull BluetoothDevice device) { - if (bluetoothGatt != null) { + @Override + public synchronized void connect(Context context, Handler handler, @NonNull String address) { + if (!isBluetoothEnabled()) { + Log.w(TAG, "Bluetooth not enabled."); + return; + } + + if (SensorType.NONE.getPreferenceValue().equals(address)) { + Log.w(TAG, "NONE: going to disconnect"); + if (isConnected()) { + disconnect(); + } + return; + } + + if (isConnected()) { Log.w(TAG, "Already connected; ignoring."); return; } + if (isSameBluetoothDevice(address)) { + return; + } else { + disconnect(); + } + + BluetoothDevice device; + try { + device = bluetoothAdapter.getRemoteDevice(address); + } catch (IllegalArgumentException e) { + Log.e(TAG, address + ": Unable to get remote device for", e); + return; + } + Log.d(TAG, device + ": trying to connect"); bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, handler); @@ -146,8 +182,10 @@ public class BluetoothConnectionManager { observer.onDisconnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress(), bluetoothGatt.getDevice().getName())); } - synchronized void disconnect() { - if (bluetoothGatt == null) { + @Override + public synchronized void disconnect() { + if (!isConnected()) { + Log.w(TAG, "Not connected; no need to re-connect."); return; } Log.i(TAG, bluetoothGatt.getDevice() + ": start disconnect"); @@ -158,11 +196,21 @@ public class BluetoothConnectionManager { bluetoothGatt = null; } - synchronized boolean isSameBluetoothDevice(String address) { + private synchronized boolean isSameBluetoothDevice(String address) { if (bluetoothGatt == null) { return false; } return address.equals(bluetoothGatt.getDevice().getAddress()); } + + + private boolean isBluetoothEnabled() { + return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); + } + + @Override + public boolean isConnected() { + return bluetoothGatt != null; + } } diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerBarometricPressure.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerBarometricPressure.java index ef7aeba37..299fa4a8b 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerBarometricPressure.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothHandlerBarometricPressure.java @@ -1,12 +1,15 @@ package de.dennisguse.opentracks.sensors; import android.bluetooth.BluetoothGattCharacteristic; +import android.util.Log; import java.util.List; import java.util.UUID; import de.dennisguse.opentracks.data.models.AtmosphericPressure; import de.dennisguse.opentracks.sensors.sensorData.Aggregator; +import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer; +import de.dennisguse.opentracks.sensors.sensorData.Raw; import de.dennisguse.opentracks.sensors.sensorData.SensorHandlerInterface; public class BluetoothHandlerBarometricPressure implements SensorHandlerInterface { @@ -23,14 +26,22 @@ public class BluetoothHandlerBarometricPressure implements SensorHandlerInterfac @Override public Aggregator, ?> createEmptySensorData(String address, String name) { - return null; //TODO + return new AggregatorBarometer(address, name); } @Override public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) { - //TODO + AtmosphericPressure value = parseEnvironmentalSensing(characteristic); + if (value == null) return; + Log.i("ZXCV", "VALUE: " + value); + observer.onChange(new Raw<>(value)); } + /** + * Decoding: + * org.bluetooth.service.environmental_sensing.xml + * org.bluetooth.characteristic.pressure.xml + */ public static AtmosphericPressure parseEnvironmentalSensing(BluetoothGattCharacteristic characteristic) { byte[] raw = characteristic.getValue(); diff --git a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java index 28f65f304..8f919fca2 100644 --- a/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java +++ b/src/main/java/de/dennisguse/opentracks/sensors/BluetoothRemoteSensorManager.java @@ -17,11 +17,9 @@ package de.dennisguse.opentracks.sensors; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.SharedPreferences; import android.os.Handler; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -30,29 +28,20 @@ import java.time.Duration; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.settings.PreferencesUtils; -import de.dennisguse.opentracks.util.PermissionRequester; /** * Bluetooth LE sensor manager: manages connections to Bluetooth LE sensors. *
* Note: should only be instantiated once. - *
* TODO: listen for Bluetooth enabled/disabled events. - *
- * TODO: In case, a cycling (Cadence and Speed) sensor reports both values, testing is required.
- * We establish two GATT separate GATT connections (as if two different sensors were used).
- * However, it is not clear if this is allowed.
- * Even if this works, it is not clear what happens if a user (while recording) changes one of the sensors in the settings as this will trigger a disconnect of one GATT.
- *
* @author Sandor Dornbush
*/
public class BluetoothRemoteSensorManager implements SensorConnector, SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
- public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(50); //TODO
+ public static final Duration MAX_SENSOR_DATE_SET_AGE = Duration.ofSeconds(5);
- private final BluetoothAdapter bluetoothAdapter;
private final Context context;
private final Handler handler;
private boolean started = false;
@@ -66,14 +55,13 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @Nullable SensorManager.SensorDataChangedObserver observer) {
this.context = context;
this.handler = handler;
- bluetoothAdapter = BluetoothUtils.getAdapter(context);
-
- this.heartRate = new BluetoothConnectionManager(observer, new BluetoothHandlerManagerHeartRate());
- this.cyclingCadence = new BluetoothConnectionManager(observer, new BluetoothHandlerCyclingCadence());
- this.cyclingSpeed = new BluetoothConnectionManager(observer, new BluetoothHandlerCyclingDistanceSpeed());
- this.cyclingPower = new BluetoothConnectionManager(observer, new BluetoothHandlerManagerCyclingPower());
- this.runningSpeedAndCadence = new BluetoothConnectionManager(observer, new BluetoothHandlerRunningSpeedAndCadence());
+ BluetoothAdapter bluetoothAdapter = BluetoothUtils.getAdapter(context);
+ this.heartRate = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerManagerHeartRate());
+ this.cyclingCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingCadence());
+ this.cyclingSpeed = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerCyclingDistanceSpeed());
+ this.cyclingPower = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerManagerCyclingPower());
+ this.runningSpeedAndCadence = new BluetoothConnectionManager(bluetoothAdapter, observer, new BluetoothHandlerRunningSpeedAndCadence());
}
@Override
@@ -95,39 +83,8 @@ public class BluetoothRemoteSensorManager implements SensorConnector, SharedPref
started = false;
}
- public boolean isEnabled() {
- return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
- }
-
private synchronized void connect(BluetoothConnectionManager connectionManager, String address) {
- if (!isEnabled()) {
- Log.w(TAG, "Bluetooth not enabled.");
- return;
- }
-
- if (PreferencesUtils.isBluetoothSensorAddressNone(address)) {
- Log.w(TAG, "No Bluetooth address.");
- connectionManager.disconnect();
- return;
- }
-
- // Check if there is an ongoing connection; if yes, check if the address changed.
- if (connectionManager.isSameBluetoothDevice(address)) {
- return;
- } else {
- connectionManager.disconnect();
- }
- if (!PermissionRequester.BLUETOOTH.hasPermission(context)) {
- Log.w(TAG, "BLUETOOTH_SCAN and/or BLUETOOTH_CONNECT not granted; not connecting.");
- }
-
- Log.i(TAG, "Connecting to bluetooth address: " + address);
- try {
- BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
- connectionManager.connect(context, handler, device);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Unable to get remote device for: " + address, e);
- }
+ connectionManager.connect(context, handler, address);
}
@Override
diff --git a/src/main/java/de/dennisguse/opentracks/sensors/GainManager.java b/src/main/java/de/dennisguse/opentracks/sensors/GainManager.java
index 40e1749f9..6dca516d4 100644
--- a/src/main/java/de/dennisguse/opentracks/sensors/GainManager.java
+++ b/src/main/java/de/dennisguse/opentracks/sensors/GainManager.java
@@ -1,13 +1,14 @@
package de.dennisguse.opentracks.sensors;
import android.content.Context;
+import android.content.SharedPreferences;
import android.os.Handler;
import android.util.Log;
-import de.dennisguse.opentracks.data.models.AtmosphericPressure;
import de.dennisguse.opentracks.sensors.driver.BarometerInternal;
+import de.dennisguse.opentracks.sensors.driver.Driver;
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
-import de.dennisguse.opentracks.sensors.sensorData.Raw;
+import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
@@ -16,31 +17,61 @@ public class GainManager implements SensorConnector {
private static final String TAG = GainManager.class.getSimpleName();
- private final BarometerInternal driver;
private final SensorManager.SensorDataChangedObserver listener;
+ private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
+ connect();
+ };
+
+ private Context context;
+ private Handler handler;
+ private Driver driver;
+
public GainManager(SensorManager.SensorDataChangedObserver listener) {
this.listener = listener;
- driver = new BarometerInternal();
}
public void start(Context context, Handler handler) {
- driver.connect(context, handler, this);
-
- if (driver.isConnected()) {
- listener.onConnect(new AggregatorBarometer("internal"));
- }
+ this.context = context;
+ this.handler = handler;
+ PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
public void stop(Context context) {
Log.d(TAG, "Stop");
+ this.context = null;
+ this.handler = null;
+ PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
- driver.disconnect(context);
- listener.onDisconnect(new AggregatorBarometer("internal"));
+ onDisconnect();
}
- public void onSensorValueChanged(AtmosphericPressure currentSensorValue) {
- listener.onChange(new Raw<>(currentSensorValue));
+ private void connect() {
+ onDisconnect();
+
+ String address = PreferencesUtils.getBarometerSensorAddress();
+ switch (PreferencesUtils.getSensorType(address)) {
+ case NONE -> driver = null;
+ case INTERNAL -> driver = new BarometerInternal(listener);
+ case REMOTE -> driver =
+ new BluetoothConnectionManager(
+ BluetoothUtils.getAdapter(context),
+ listener,
+ new BluetoothHandlerBarometricPressure()
+ );
+ default -> throw new RuntimeException("Not implemented");
+ }
+
+ if (driver != null) {
+ driver.connect(context, handler, address);
+ }
+ }
+
+ private void onDisconnect() {
+ if (driver == null) return;
+
+ driver.disconnect();
+ listener.onDisconnect(new AggregatorBarometer("GainManager", null));
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/sensors/SensorType.java b/src/main/java/de/dennisguse/opentracks/sensors/SensorType.java
new file mode 100644
index 000000000..ad66d7fce
--- /dev/null
+++ b/src/main/java/de/dennisguse/opentracks/sensors/SensorType.java
@@ -0,0 +1,17 @@
+package de.dennisguse.opentracks.sensors;
+
+public enum SensorType {
+ NONE("NONE"),
+ INTERNAL("INTERNAL"),
+ REMOTE("*");
+
+ private final String preferenceValue;
+
+ SensorType(String preferenceValue) {
+ this.preferenceValue = preferenceValue;
+ }
+
+ public String getPreferenceValue() {
+ return preferenceValue;
+ }
+}
diff --git a/src/main/java/de/dennisguse/opentracks/sensors/driver/BarometerInternal.java b/src/main/java/de/dennisguse/opentracks/sensors/driver/BarometerInternal.java
index 9032010a5..74ef061f8 100644
--- a/src/main/java/de/dennisguse/opentracks/sensors/driver/BarometerInternal.java
+++ b/src/main/java/de/dennisguse/opentracks/sensors/driver/BarometerInternal.java
@@ -4,24 +4,29 @@ import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
import android.os.Handler;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import java.util.concurrent.TimeUnit;
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
-import de.dennisguse.opentracks.sensors.GainManager;
+import de.dennisguse.opentracks.sensors.SensorManager;
+import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
+import de.dennisguse.opentracks.sensors.sensorData.Raw;
-public class BarometerInternal {
+public class BarometerInternal implements Driver {
private static final String TAG = BarometerInternal.class.getSimpleName();
private static final int SAMPLING_PERIOD = (int) TimeUnit.SECONDS.toMicros(5);
- private GainManager observer;
+ private final SensorManager.SensorDataChangedObserver listener;
- private final SensorEventListener listener = new SensorEventListener() {
+ private Context context;
+
+ private final SensorEventListener sensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
if (!isConnected()) {
@@ -29,7 +34,7 @@ public class BarometerInternal {
return;
}
- observer.onSensorValueChanged(AtmosphericPressure.ofHPA(event.values[0]));
+ listener.onChange(new Raw<>(AtmosphericPressure.ofHPA(event.values[0])));
}
@Override
@@ -38,30 +43,40 @@ public class BarometerInternal {
}
};
- public void connect(Context context, Handler handler, GainManager observer) {
- SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ public BarometerInternal(@NonNull SensorManager.SensorDataChangedObserver listener) {
+ this.listener = listener;
+
+ }
+
+ @Override
+ public void connect(Context context, Handler handler, String addressIgnored) {
+
+ android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
if (pressureSensor == null) {
Log.w(TAG, "No pressure sensor available.");
- this.observer = null;
return;
}
- if (sensorManager.registerListener(listener, pressureSensor, SAMPLING_PERIOD, handler)) {
- this.observer = observer;
+ if (sensorManager.registerListener(sensorEventListener, pressureSensor, SAMPLING_PERIOD, handler)) {
+ this.context = context;
+ listener.onConnect(new AggregatorBarometer("internal", null));
return;
}
- disconnect(context);
- }
-
- public void disconnect(Context context) {
- SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
- sensorManager.unregisterListener(listener);
- observer = null;
+ disconnect();
}
public boolean isConnected() {
- return observer != null;
+ return context != null;
+ }
+
+ @Override
+ public void disconnect() {
+ if (!isConnected()) return;
+
+ android.hardware.SensorManager sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ sensorManager.unregisterListener(sensorEventListener);
+ this.context = null;
}
}
diff --git a/src/main/java/de/dennisguse/opentracks/sensors/driver/Driver.java b/src/main/java/de/dennisguse/opentracks/sensors/driver/Driver.java
new file mode 100644
index 000000000..98157de60
--- /dev/null
+++ b/src/main/java/de/dennisguse/opentracks/sensors/driver/Driver.java
@@ -0,0 +1,12 @@
+package de.dennisguse.opentracks.sensors.driver;
+
+import android.content.Context;
+import android.os.Handler;
+
+public interface Driver {
+ void connect(Context context, Handler handler, String address);
+
+ boolean isConnected();
+
+ void disconnect();
+}
diff --git a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorBarometer.java b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorBarometer.java
index 7e5ed7c57..06f9fbb7e 100644
--- a/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorBarometer.java
+++ b/src/main/java/de/dennisguse/opentracks/sensors/sensorData/AggregatorBarometer.java
@@ -11,8 +11,8 @@ public class AggregatorBarometer extends Aggregator