Compare commits

...

10 Commits

Author SHA1 Message Date
Dennis Guse 8d7a8719bc Release: v4.9.9 2023-12-27 10:32:39 +01:00
Dennis Guse 8f5388edce While recording: show BLE sensor name instead of address.
Fixes #1796.
2023-12-26 11:22:05 +01:00
Dennis Guse 81a78820cc Merge pull request #1793 from OpenTracksApp/crash#1708
Bugfix: sensors were started twice.
2023-12-26 09:04:29 +01:00
Dennis Guse 2fe452c77c Revert "fix NPE at null barometer after unregister listener"
This reverts commit 5dbd17f3bb.
2023-12-26 08:42:38 +01:00
Dennis Guse 826cf0d468 Bugfix: TrackListActivity couldn't stop and restart sensors.
Part of #1780.
2023-12-26 08:42:38 +01:00
Dennis Guse 6cd627e832 Bugfix: TrackRecordingService can only start sensors once.
Fixes #1780.
2023-12-26 08:42:37 +01:00
Dennis Guse 0ee4a4e72f Bugfix: TrackListActivity triggered TrackRecordingService twice (startSensors and startRecording).
Part of #1780.
2023-12-25 20:49:49 +01:00
Dennis Guse 150cbee95b BarometerInternal: if there is no internal barometer - don't register a listener. 2023-12-25 18:48:14 +01:00
Dennis Guse 47ceb297c9 SensorManager: crash if started twice.
Part of #1780.
2023-12-24 22:10:58 +01:00
Dennis Guse aed4af5736 Bluetooth logs: make them slightly more readable. 2023-12-24 21:49:48 +01:00
21 changed files with 95 additions and 86 deletions
+2 -2
View File
@@ -64,8 +64,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 5563
versionName "v4.9.8"
versionCode 5573
versionName "v4.9.9"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -0,0 +1,6 @@
v4.9.9: OpenTracks
Bugfix:
- Crash when using a BLE sensor on recording end (race condition)
Reason: sensor subsystem was started twice and only the 2nd one was shutdown.
- While recording, BLE sensor names are shown again
@@ -25,6 +25,7 @@ import android.graphics.drawable.AnimatedVectorDrawable;
import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -73,7 +74,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
private static final String TAG = TrackListActivity.class.getSimpleName();
// The following are set in onCreate
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackRecordingServiceConnection recordingStatusConnection;
private TrackListAdapter adapter;
private TrackListBinding viewBinding;
@@ -138,13 +139,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
.observe(TrackListActivity.this, this::onGpsStatusChanged);
updateGpsMenuItem(true, recordingStatus.isRecording());
if (service.getGpsStatusObservable().getValue().isGpsStarted()) {
return;
}
//TODO Not cool to do this in a callback that might be called more than once!
service.tryStartSensors();
};
@Override
@@ -154,7 +148,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
requestRequiredPermissions();
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
recordingStatusConnection = new TrackRecordingServiceConnection(bindChangedCallback);
viewBinding.aggregatedStatsButton.setOnClickListener((view) -> startActivity(IntentUtils.newIntent(this, AggregatedStatisticsActivity.class)));
viewBinding.sensorStartButton.setOnClickListener((view) -> {
@@ -163,9 +157,14 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
} else {
if (gpsStatusValue.isGpsStarted()) {
trackRecordingServiceConnection.unbindAndStop(this);
recordingStatusConnection.unbindAndStop(this);
recordingStatusConnection.startConnection(this); //TODO We need to stay listening!
} else {
trackRecordingServiceConnection.startAndBindWithCallback(this);
new TrackRecordingServiceConnection((service, connection) -> {
service.tryStartSensors();
connection.unbind(this);
}).startAndBindWithCallback(this);
}
}
});
@@ -182,6 +181,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
}
// Not Recording -> Recording
Log.i(TAG, "Starting recording");
updateGpsMenuItem(false, true);
new TrackRecordingServiceConnection((service, connection) -> {
Track.Id trackId = service.startNewTrack();
@@ -201,7 +201,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
// Recording -> Stop
ActivityUtils.vibrate(this, 1000);
updateGpsMenuItem(false, false);
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
recordingStatusConnection.stopRecording(TrackListActivity.this);
viewBinding.trackListFabAction.setImageResource(R.drawable.ic_baseline_record_24);
viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.red_dark));
return true;
@@ -220,7 +220,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
super.onStart();
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.startConnection(this);
recordingStatusConnection.startConnection(this);
}
@Override
@@ -240,14 +240,14 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
super.onStop();
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackRecordingServiceConnection.unbind(this);
recordingStatusConnection.unbind(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
viewBinding = null;
trackRecordingServiceConnection = null;
recordingStatusConnection = null;
adapter = null;
}
@@ -47,24 +47,23 @@ public class BluetoothConnectionManager {
private final SensorHandlerInterface sensorHandler;
private BluetoothGatt bluetoothGatt;
private final BluetoothGattCallback connectCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTING ->
Log.i(TAG, "Connecting to sensor: " + gatt.getDevice());
Log.i(TAG, gatt.getDevice() + ": connecting to sensor");
case BluetoothProfile.STATE_CONNECTED -> {
Log.i(TAG, "Connected to sensor: " + gatt.getDevice() + "; discovering services.");
Log.i(TAG, gatt.getDevice() + ": connected to sensor; discovering services");
gatt.discoverServices();
}
case BluetoothProfile.STATE_DISCONNECTING ->
Log.i(TAG, "Disconnecting from sensor: " + gatt.getDevice());
Log.i(TAG, gatt.getDevice() + ": disconnecting from sensor: ");
case BluetoothProfile.STATE_DISCONNECTED -> {
//This is also triggered, if no connection was established (ca. 30s)
Log.i(TAG, "Disconnected from sensor: " + gatt.getDevice() + "; trying to reconnect");
Log.i(TAG, gatt.getDevice() + ": disconnected from sensor: trying to reconnect");
if (gatt.connect()) {
Log.e(TAG, "Could not trigger reconnect for sensor: " + gatt.getDevice());
Log.e(TAG, gatt.getDevice() + ": could not trigger reconnect for sensor");
}
clearData();
}
@@ -84,13 +83,13 @@ public class BluetoothConnectionManager {
}
if (gattService == null) {
Log.e(TAG, "Could not get gattService for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement);
Log.e(TAG, gatt.getDevice() + ": could not get gattService for serviceUUID=" + serviceMeasurement);
return;
}
BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(serviceMeasurement.measurementUUID());
if (characteristic == null) {
Log.e(TAG, "Could not get BluetoothCharacteristic for address=" + gatt.getDevice().getAddress() + " serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.measurementUUID());
Log.e(TAG, gatt.getDevice() + ": could not get BluetoothCharacteristic for serviceUUID=" + serviceMeasurement.serviceUUID() + " characteristicUUID=" + serviceMeasurement.measurementUUID());
return;
}
gatt.setCharacteristicNotification(characteristic, true);
@@ -110,13 +109,14 @@ public class BluetoothConnectionManager {
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) {
UUID serviceUUID = characteristic.getService().getUuid();
Log.d(TAG, "Received data from " + gatt.getDevice().getAddress() + " with service " + serviceUUID + " and characteristics " + characteristic.getUuid());
BluetoothDevice device = gatt.getDevice();
Log.d(TAG, device + ": Received data with service " + serviceUUID + " and characteristics " + characteristic.getUuid());
Optional<ServiceMeasurementUUID> serviceMeasurementUUID = sensorHandler.getServices()
.stream()
.filter(s -> s.serviceUUID().equals(characteristic.getService().getUuid()))
.findFirst();
if (serviceMeasurementUUID.isEmpty()) {
Log.e(TAG, "Unknown service UUID; not supported?");
Log.e(TAG, device + ": Unknown service UUID; not supported?");
return;
}
@@ -135,24 +135,26 @@ public class BluetoothConnectionManager {
return;
}
Log.d(TAG, "Connecting to: " + device);
Log.d(TAG, device + ": trying to connect");
bluetoothGatt = device.connectGatt(context, false, connectCallback, BluetoothDevice.TRANSPORT_AUTO, 0, handler);
observer.onConnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
observer.onConnect(sensorHandler.createEmptySensorData(device.getAddress(), device.getName()));
}
private synchronized void clearData() {
observer.onDisconnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
observer.onDisconnect(sensorHandler.createEmptySensorData(bluetoothGatt.getDevice().getAddress(), bluetoothGatt.getDevice().getName()));
}
synchronized void disconnect() {
if (bluetoothGatt == null) {
Log.w(TAG, "Cannot disconnect if not connected.");
return;
}
Log.i(TAG, bluetoothGatt.getDevice() + ": start disconnect");
bluetoothGatt.disconnect();
bluetoothGatt.close();
clearData();
Log.i(TAG, bluetoothGatt.getDevice() + ": disconnect finished");
bluetoothGatt = null;
}
@@ -22,7 +22,7 @@ public class BluetoothHandlerBarometricPressure implements SensorHandlerInterfac
}
@Override
public Aggregator<?, ?> createEmptySensorData(String address) {
public Aggregator<?, ?> createEmptySensorData(String address, String name) {
return null; //TODO
}
@@ -24,8 +24,8 @@ public class BluetoothHandlerCyclingCadence implements SensorHandlerInterface {
}
@Override
public AggregatorCyclingCadence createEmptySensorData(String address) {
return new AggregatorCyclingCadence(address);
public AggregatorCyclingCadence createEmptySensorData(String address, String name) {
return new AggregatorCyclingCadence(address, name);
}
@Override
@@ -26,8 +26,8 @@ public class BluetoothHandlerCyclingDistanceSpeed implements SensorHandlerInterf
}
@Override
public AggregatorCyclingDistanceSpeed createEmptySensorData(String address) {
return new AggregatorCyclingDistanceSpeed(address);
public AggregatorCyclingDistanceSpeed createEmptySensorData(String address, String name) {
return new AggregatorCyclingDistanceSpeed(address, name);
}
@Override
@@ -26,8 +26,8 @@ public class BluetoothHandlerManagerCyclingPower implements SensorHandlerInterfa
}
@Override
public AggregatorCyclingPower createEmptySensorData(String address) {
return new AggregatorCyclingPower(address);
public AggregatorCyclingPower createEmptySensorData(String address, String name) {
return new AggregatorCyclingPower(address, name);
}
@Override
@@ -36,8 +36,8 @@ public class BluetoothHandlerManagerHeartRate implements SensorHandlerInterface
}
@Override
public AggregatorHeartRate createEmptySensorData(String address) {
return new AggregatorHeartRate(address);
public AggregatorHeartRate createEmptySensorData(String address, String name) {
return new AggregatorHeartRate(address, name);
}
@Override
@@ -29,8 +29,8 @@ public class BluetoothHandlerRunningSpeedAndCadence implements SensorHandlerInte
}
@Override
public AggregatorRunning createEmptySensorData(String address) {
return new AggregatorRunning(address);
public AggregatorRunning createEmptySensorData(String address, String name) {
return new AggregatorRunning(address, name);
}
@Override
@@ -57,6 +57,10 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
}
public void start(Context context, Handler handler) {
if (gpsManager != null) {
throw new RuntimeException("SensorManager cannot be started twice; stop first.");
}
gpsManager = new GPSManager(observer); //TODO Pass listener
altitudeSumManager = new GainManager(listener);
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, listener);
@@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit;
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
import de.dennisguse.opentracks.sensors.GainManager;
public class BarometerInternal implements SensorEventListener {
public class BarometerInternal {
private static final String TAG = BarometerInternal.class.getSimpleName();
@@ -21,20 +21,22 @@ public class BarometerInternal implements SensorEventListener {
private GainManager observer;
@Override
public void onSensorChanged(SensorEvent event) {
if (!isConnected()) {
Log.w(TAG, "Not connected to sensor, cannot process data.");
return;
private final SensorEventListener listener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
if (!isConnected()) {
Log.w(TAG, "Not connected to sensor, cannot process data.");
return;
}
observer.onSensorValueChanged(AtmosphericPressure.ofHPA(event.values[0]));
}
observer.onSensorValueChanged(AtmosphericPressure.ofHPA(event.values[0]));
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.w(TAG, "Sensor accuracy changes are (currently) ignored.");
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.w(TAG, "Sensor accuracy changes are (currently) ignored.");
}
};
public void connect(Context context, Handler handler, GainManager observer) {
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
@@ -42,9 +44,10 @@ public class BarometerInternal implements SensorEventListener {
if (pressureSensor == null) {
Log.w(TAG, "No pressure sensor available.");
this.observer = null;
return;
}
if (sensorManager.registerListener(this, pressureSensor, SAMPLING_PERIOD, handler)) {
if (sensorManager.registerListener(listener, pressureSensor, SAMPLING_PERIOD, handler)) {
this.observer = observer;
return;
}
@@ -54,7 +57,7 @@ public class BarometerInternal implements SensorEventListener {
public void disconnect(Context context) {
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
sensorManager.unregisterListener(this);
sensorManager.unregisterListener(listener);
observer = null;
}
@@ -14,10 +14,6 @@ public class AggregatorCyclingCadence extends Aggregator<BluetoothHandlerCycling
private final String TAG = AggregatorCyclingCadence.class.getSimpleName();
public AggregatorCyclingCadence(String sensorAddress) {
super(sensorAddress);
}
public AggregatorCyclingCadence(String sensorAddress, String sensorName) {
super(sensorAddress, sensorName);
}
@@ -17,10 +17,6 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<BluetoothHandlerC
private Distance wheelCircumference;
public AggregatorCyclingDistanceSpeed(String sensorAddress) {
super(sensorAddress);
}
public AggregatorCyclingDistanceSpeed(String sensorAddress, String sensorName) {
super(sensorAddress, sensorName);
}
@@ -6,10 +6,6 @@ import de.dennisguse.opentracks.data.models.Power;
public class AggregatorCyclingPower extends Aggregator<Power, Power> {
public AggregatorCyclingPower(String address) {
super(address);
}
public AggregatorCyclingPower(String name, String address) {
super(name, address);
}
@@ -6,10 +6,6 @@ import de.dennisguse.opentracks.data.models.HeartRate;
public class AggregatorHeartRate extends Aggregator<HeartRate, HeartRate> {
public AggregatorHeartRate(String address) {
super(address);
}
public AggregatorHeartRate(String name, String address) {
super(name, address);
}
@@ -14,10 +14,6 @@ public final class AggregatorRunning extends Aggregator<BluetoothHandlerRunningS
private static final String TAG = AggregatorRunning.class.getSimpleName();
public AggregatorRunning(String sensorAddress) {
super(sensorAddress);
}
public AggregatorRunning(String sensorAddress, String sensorName) {
super(sensorAddress, sensorName);
}
@@ -188,7 +188,7 @@ public final class SensorDataSet {
}
private void set(@NonNull Aggregator<?, ?> type, @Nullable Aggregator<?, ?> sensorData) {
Log.i(TAG, "Setting aggregator " + type.getClass().getCanonicalName());
Log.i(TAG, "Setting aggregator " + type.getClass().getCanonicalName() + " to " + sensorData);
if (type instanceof AggregatorHeartRate) {
heartRate = (AggregatorHeartRate) sensorData;
@@ -11,7 +11,7 @@ public interface SensorHandlerInterface {
List<ServiceMeasurementUUID> getServices();
Aggregator<?, ?> createEmptySensorData(String address);
Aggregator<?, ?> createEmptySensorData(String address, String name);
void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic);
}
@@ -125,6 +125,9 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
if (isRecording()) {
endCurrentTrack();
}
if (isSensorStarted()) {
stopSensors();
}
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(this);
@@ -163,6 +166,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
Log.w(TAG, "Ignore startNewTrack. Already recording.");
return null;
}
Log.i(TAG, "startNewTrack");
// Set recording status
Track.Id trackId = trackRecordingManager.startNewTrack();
@@ -177,6 +181,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
Log.w(TAG, "Cannot resume a non-existing track.");
return;
}
Log.i(TAG, "resumeTrack");
updateRecordingStatus(RecordingStatus.record(trackId));
@@ -193,12 +198,19 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
public void tryStartSensors() {
if (isRecording()) return;
if (isSensorStarted()) return;
Log.i(TAG, "tryStartSensors");
startSensors();
}
private void startSensors() {
private synchronized void startSensors() {
if (isSensorStarted()) {
Log.i(TAG, "sensors already started; skipping");
return;
}
Log.i(TAG, "startSensors");
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
trackPointCreator.start(this, handler);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -239,6 +251,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
stopForeground(true);
notificationManager.cancelNotification();
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
gpsStatusObservable.postValue(STATUS_GPS_DEFAULT);
}
@Override
@@ -255,7 +268,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
@Override
public void newGpsStatus(GpsStatusValue gpsStatusValue) {
Log.e(TAG, "newGpsStatus: " + gpsStatusValue.message);
Log.i(TAG, "newGpsStatus: " + gpsStatusValue.message);
if (notificationManager == null) {
@@ -336,6 +349,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
return recordingStatus.isRecording();
}
private boolean isSensorStarted() {
return wakeLock != null;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) {
voiceAnnouncementManager.onSharedPreferenceChanged(sharedPreferences, key);
@@ -127,9 +127,6 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
setTrackRecordingService(null);
}
/**
* Unbinds and stops the service.
*/
public void unbindAndStop(Context context) {
unbind(context);
context.stopService(new Intent(context, TrackRecordingService.class));
@@ -153,7 +150,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
Log.i(TAG, "Connected to the service.");
Log.i(TAG, "Connected to the service: " + service);
try {
service.linkToDeath(this, 0);
} catch (RemoteException e) {