mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-02 01:23:06 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90d81c4d84 | |||
| e047100e34 | |||
| 80d7f8d92e | |||
| d038f5c33a | |||
| c6b4e738b9 | |||
| 5de2012452 | |||
| 0584f05ddc | |||
| 8c9e3882c7 | |||
| 310ac8c06e |
@@ -44,6 +44,7 @@ For cycling (incl. cadence):
|
||||
* QUARQ Red DZero Powermeter
|
||||
* Tacx Satori Smart
|
||||
* Wahoo Kickr v4.0
|
||||
* Magene PES P505
|
||||
* Rotor 2INpower DM Road (only supports power measurement, cadence measurement is proprietary @ firmware v1.061)
|
||||
|
||||
For running:
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.2.0'
|
||||
classpath 'com.android.tools.build:gradle:8.2.1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 5602
|
||||
versionName "v4.10.0"
|
||||
versionCode 5611
|
||||
versionName "v4.10.1"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
v4.10.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Android 14: crash on recording start if permissions are not granted
|
||||
- Chart titles are more compact
|
||||
|
||||
Bugfix:
|
||||
- BLE sensor re-connect resulted in crash (introduced in v4.9.6)
|
||||
- Cycling power was not processed (introduced in v4.9.6)
|
||||
- Cycling cadence as well as distance allow for repeated data
|
||||
- BLE barometer data was not processed correctly
|
||||
+1
-1
@@ -20,6 +20,6 @@ public class BluetoothHandlerBarometricPressureTest {
|
||||
AtmosphericPressure pressure = BluetoothHandlerBarometricPressure.parseEnvironmentalSensing(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(AtmosphericPressure.ofPA(100165), pressure);
|
||||
assertEquals(AtmosphericPressure.ofHPA(1001.65f), pressure);
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ abstract class ChartValueSeries {
|
||||
// Make copies of the stroke paint with the default thickness
|
||||
titlePaint = new Paint(strokePaint);
|
||||
titlePaint.setTextSize(fontSizeMedium);
|
||||
titlePaint.setTextAlign(Align.CENTER);
|
||||
titlePaint.setTextAlign(Align.LEFT);
|
||||
titlePaint.setStyle(Style.FILL_AND_STROKE);
|
||||
|
||||
markerPaint = new Paint(strokePaint);
|
||||
|
||||
@@ -43,6 +43,7 @@ import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
@@ -666,24 +667,31 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
private record TitlePosition(
|
||||
int line, // line number (starts at 1, top to bottom numbering)
|
||||
int xPos // x position in points (starts at 0, left to right indexing)
|
||||
) {};
|
||||
private record TitleDimensions(
|
||||
int lineCount, // number of lines the titles will take
|
||||
int lineHeight, // height of a line (all lines have the same height)
|
||||
List<TitlePosition> titlePositions // positions of visible titles (the order corresponds to seriesList)
|
||||
) {};
|
||||
|
||||
/**
|
||||
* Draws series titles.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
private void drawSeriesTitles(Canvas canvas) {
|
||||
int[] titleDimensions = getTitleDimensions();
|
||||
int lines = titleDimensions[0];
|
||||
int lineHeight = titleDimensions[1];
|
||||
int count = 0;
|
||||
TitleDimensions td = getTitleDimensions();
|
||||
Iterator<TitlePosition> tpI = td.titlePositions.iterator();
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
|
||||
count++;
|
||||
String title = getContext().getString(chartValueSeries.getTitleId(unitSystem));
|
||||
Paint paint = chartValueSeries.getTitlePaint();
|
||||
int x = (int) (0.5 * width) + getScrollX();
|
||||
int y = topBorder - spacer - (lines - count) * (lineHeight + spacer);
|
||||
canvas.drawText(title, x, y, paint);
|
||||
TitlePosition tp = tpI.next();
|
||||
int y = topBorder - spacer - (td.lineCount - tp.line) * (td.lineHeight + spacer);
|
||||
canvas.drawText(title, tp.xPos + getScrollX(), y, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -692,20 +700,27 @@ public class ChartView extends View {
|
||||
* Gets the title dimensions.
|
||||
* Returns an array of 2 integers, first element is the number of lines and the second element is the line height.
|
||||
*/
|
||||
private int[] getTitleDimensions() {
|
||||
int lines = 0;
|
||||
private TitleDimensions getTitleDimensions() {
|
||||
int lineCnt = 1;
|
||||
int lineHeight = 0;
|
||||
List<TitlePosition> tps = new ArrayList<TitlePosition>();
|
||||
int xPosInLine = spacer;
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
|
||||
lines++;
|
||||
String title = getContext().getString(chartValueSeries.getTitleId(unitSystem));
|
||||
Rect rect = getRect(chartValueSeries.getTitlePaint(), title);
|
||||
if (rect.height() > lineHeight) {
|
||||
lineHeight = rect.height();
|
||||
if (rect.height() > lineHeight) lineHeight = rect.height();
|
||||
int xNextPosInLine = xPosInLine + rect.width() + 2*spacer;
|
||||
// if second or later title does not fully fit on this line then print it on the next line
|
||||
if (xPosInLine > spacer && xNextPosInLine-spacer > width) {
|
||||
lineCnt++;
|
||||
xPosInLine = spacer;
|
||||
}
|
||||
tps.add(new TitlePosition(lineCnt, xPosInLine));
|
||||
xPosInLine += rect.width() + 2*spacer;
|
||||
}
|
||||
}
|
||||
return new int[]{lines, lineHeight};
|
||||
return new TitleDimensions(lineCnt, lineHeight, tps);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -932,8 +947,8 @@ public class ChartView extends View {
|
||||
}
|
||||
|
||||
leftBorder = (int) (density * BORDER + markerLength);
|
||||
int[] titleDimensions = getTitleDimensions();
|
||||
topBorder = (int) (density * BORDER + titleDimensions[0] * (titleDimensions[1] + spacer));
|
||||
TitleDimensions td = getTitleDimensions();
|
||||
topBorder = (int) (density * BORDER + td.lineCount * (td.lineHeight + spacer));
|
||||
Rect xAxisLabelRect = getRect(axisPaint, getXAxisLabel());
|
||||
// border + x axis marker + spacer + .5 x axis label
|
||||
bottomBorder = (int) (density * BORDER + getRect(xAxisMarkerPaint, "1").height() + spacer + (xAxisLabelRect.height() / 2));
|
||||
|
||||
@@ -3,7 +3,7 @@ package de.dennisguse.opentracks.data.models;
|
||||
public record AtmosphericPressure(float value) {
|
||||
|
||||
public static AtmosphericPressure ofPA(float value_Pa) {
|
||||
return new AtmosphericPressure(value_Pa * 100);
|
||||
return new AtmosphericPressure(value_Pa / 100);
|
||||
}
|
||||
|
||||
public static AtmosphericPressure ofHPA(float value_hPa) {
|
||||
|
||||
@@ -148,6 +148,7 @@ public class BluetoothConnectionManager implements Driver {
|
||||
Log.w(TAG, "NONE: going to disconnect");
|
||||
if (isConnected()) {
|
||||
disconnect();
|
||||
observer.onRemove(sensorHandler.createEmptySensorData(null, null));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -33,7 +32,7 @@ public class BluetoothHandlerBarometricPressure implements SensorHandlerInterfac
|
||||
public void handlePayload(SensorManager.SensorDataChangedObserver observer, ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
AtmosphericPressure value = parseEnvironmentalSensing(characteristic);
|
||||
if (value == null) return;
|
||||
Log.i("ZXCV", "VALUE: " + value);
|
||||
|
||||
observer.onChange(new Raw<>(value));
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,11 @@ public class GainManager implements SensorConnector {
|
||||
|
||||
String address = PreferencesUtils.getBarometerSensorAddress();
|
||||
switch (PreferencesUtils.getSensorType(address)) {
|
||||
case NONE -> driver = null;
|
||||
case NONE -> {
|
||||
driver = null;
|
||||
listener.onRemove(new AggregatorBarometer(null, null));
|
||||
return;
|
||||
}
|
||||
case INTERNAL -> driver = new BarometerInternal(listener);
|
||||
case REMOTE -> driver =
|
||||
new BluetoothConnectionManager(
|
||||
@@ -63,9 +67,7 @@ public class GainManager implements SensorConnector {
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
if (driver != null) {
|
||||
driver.connect(context, handler, address);
|
||||
}
|
||||
driver.connect(context, handler, address);
|
||||
}
|
||||
|
||||
private void onDisconnect() {
|
||||
|
||||
@@ -28,20 +28,26 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
|
||||
private final SensorDataChangedObserver listener = new SensorDataChangedObserver() {
|
||||
|
||||
@Override
|
||||
public void onConnect(Aggregator<?, ?> sensorData) {
|
||||
sensorDataSet.add(sensorData);
|
||||
public void onConnect(Aggregator<?, ?> aggregator) {
|
||||
sensorDataSet.add(aggregator);
|
||||
observer.onChange(new SensorDataSet(sensorDataSet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(Raw<?> sensorData) {
|
||||
sensorDataSet.update(sensorData);
|
||||
public void onChange(Raw<?> data) {
|
||||
sensorDataSet.update(data);
|
||||
observer.onChange(new SensorDataSet(sensorDataSet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(Aggregator<?, ?> sensorData) {
|
||||
sensorDataSet.remove(sensorData);
|
||||
public void onDisconnect(Aggregator<?, ?> aggregator) {
|
||||
sensorDataSet.add(aggregator);
|
||||
observer.onChange(new SensorDataSet(sensorDataSet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(Aggregator<?, ?> aggregator) {
|
||||
sensorDataSet.remove(aggregator);
|
||||
observer.onChange(new SensorDataSet(sensorDataSet));
|
||||
}
|
||||
};
|
||||
@@ -134,5 +140,7 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
|
||||
void onChange(Raw<?> sensorData);
|
||||
|
||||
void onDisconnect(Aggregator<?, ?> sensorData);
|
||||
|
||||
void onRemove(Aggregator<?, ?> sensorData);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -23,7 +23,11 @@ public class AggregatorCyclingCadence extends Aggregator<BluetoothHandlerCycling
|
||||
if (previous != null) {
|
||||
float timeDiff_ms = UintUtils.diff(current.value().crankRevolutionsTime(), previous.value().crankRevolutionsTime(), UintUtils.UINT16_MAX) / 1024f * 1000;
|
||||
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
||||
if (timeDiff.isZero() || timeDiff.isNegative()) {
|
||||
|
||||
if (timeDiff.isZero()) {
|
||||
return;
|
||||
}
|
||||
if (timeDiff.isNegative()) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
value = null;
|
||||
return;
|
||||
|
||||
+6
-2
@@ -26,8 +26,12 @@ public class AggregatorCyclingDistanceSpeed extends Aggregator<BluetoothHandlerC
|
||||
if (previous != null) {
|
||||
float timeDiff_ms = UintUtils.diff(current.value().wheelRevolutionsTime(), previous.value().wheelRevolutionsTime(), UintUtils.UINT16_MAX) / 1024f * 1000;
|
||||
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
||||
if (timeDiff.isZero() || timeDiff.isNegative()) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute speed.");
|
||||
|
||||
if (timeDiff.isZero()) {
|
||||
return;
|
||||
}
|
||||
if (timeDiff.isNegative()) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
+4
-3
@@ -3,16 +3,17 @@ package de.dennisguse.opentracks.sensors.sensorData;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerManagerCyclingPower;
|
||||
|
||||
public class AggregatorCyclingPower extends Aggregator<Power, Power> {
|
||||
public class AggregatorCyclingPower extends Aggregator<BluetoothHandlerManagerCyclingPower.Data, Power> {
|
||||
|
||||
public AggregatorCyclingPower(String name, String address) {
|
||||
super(name, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeValue(Raw<Power> current) {
|
||||
this.value = current.value();
|
||||
public void computeValue(Raw<BluetoothHandlerManagerCyclingPower.Data> current) {
|
||||
this.value = current.value().power();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.annotation.VisibleForTesting;
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
||||
@@ -116,7 +115,7 @@ public final class SensorDataSet {
|
||||
return;
|
||||
}
|
||||
if (value instanceof BluetoothHandlerManagerCyclingPower.Data) {
|
||||
this.cyclingPower.add((Raw<Power>) data);
|
||||
this.cyclingPower.add((Raw<BluetoothHandlerManagerCyclingPower.Data>) data);
|
||||
return;
|
||||
}
|
||||
if (value instanceof AtmosphericPressure) {
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.os.Looper;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
@@ -38,7 +37,6 @@ import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
@@ -217,8 +215,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
if (!PermissionRequester.RECORDING.hasPermission(this)) {
|
||||
Toast.makeText(this, R.string.permission_recording_failed, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
throw new RuntimeException("Android14: Please grant permissions LOCATION and NEARBY DEVICES (manually)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -30,7 +30,6 @@ public class AltitudeCorrectionManager {
|
||||
|
||||
public void correctAltitude(Context context, TrackPoint trackPoint) {
|
||||
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
|
||||
Log.d(TAG, "No altitude correction necessary.");
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && altitudeConverter != null) {
|
||||
|
||||
Reference in New Issue
Block a user