forked from upstream-mirrors/OpenTracks
Merge upstream updates into local fork
This commit is contained in:
@@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
@@ -14,13 +15,13 @@ public enum ActivityType {
|
||||
|
||||
AIRPLANE("airplane", R.string.activity_type_airplane, R.drawable.ic_activity_flight_24dp, true),
|
||||
ATV("ATV", R.string.activity_type_atv, R.drawable.ic_activity_drive_24dp, true),
|
||||
BIKING("biking", R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
BIKING("biking", List.of("bike", "indoor cycling"), R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
BLIMP("blimp", R.string.activity_type_blimp, ActivityIcon.ICON_UNKNOWN, true),
|
||||
BOAT("boat", R.string.activity_type_boat, R.drawable.ic_activity_boat_24dp, true),
|
||||
CLIMBING("climbing", R.string.activity_type_climbing, R.drawable.ic_activity_climbing_24dp, false),
|
||||
COMMERCIAL_AIRPLANE("commercial airplane", R.string.activity_type_commercial_airplane, R.drawable.ic_activity_flight_24dp, true),
|
||||
CROSS_COUNTRY_SKIING("cross-country skiing", R.string.activity_type_cross_country_skiing, R.drawable.ic_activity_skiing_24dp, true),
|
||||
CYCLING("cycling", R.string.activity_type_cycling, R.drawable.ic_activity_bike_24dp, true),
|
||||
CYCLING("cycling", List.of("cycle"), R.string.activity_type_cycling, R.drawable.ic_activity_bike_24dp, true),
|
||||
DIRT_BIKE("dirt bike", R.string.activity_type_dirt_bike, R.drawable.ic_activity_mtb_24dp, true),
|
||||
DONKEY_BACK_RIDING("donkey back riding", R.string.activity_type_donkey_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
DRIVING("driving", R.string.activity_type_driving, R.drawable.ic_activity_drive_24dp, true),
|
||||
@@ -30,21 +31,21 @@ public enum ActivityType {
|
||||
FERRY("ferry", R.string.activity_type_ferry, R.drawable.ic_activity_boat_24dp, true),
|
||||
FRISBEE("frisbee", R.string.activity_type_frisbee, ActivityIcon.ICON_UNKNOWN, true),
|
||||
GLIDING("gliding", R.string.activity_type_gliding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
GRAVEL_BIKING("gravel biking", R.string.activity_type_gravel_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
GRAVEL_BIKING("gravel biking", List.of("gravel bike"), R.string.activity_type_gravel_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
HANG_GLIDING("hang gliding", R.string.activity_type_hang_gliding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
HELICOPTER("helicopter", R.string.activity_type_helicopter, ActivityIcon.ICON_UNKNOWN, true),
|
||||
HIKING("hiking", R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
HIKING("hiking", List.of("hike"), R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
HORSE_BACK_RIDING("horse back riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
HOT_AIR_BALLOON("hot air balloon", R.string.activity_type_hot_air_balloon, ActivityIcon.ICON_UNKNOWN, true),
|
||||
ICE_SAILING("ice sailing", R.string.activity_type_ice_sailing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
INLINE_SKATING("inline skating", R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp, true),
|
||||
INLINE_SKATING("inline skating", List.of("inline skate"), R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp, true),
|
||||
KAYAKING("kayaking", R.string.activity_type_kayaking, R.drawable.ic_activity_kayaking_24dp, true),
|
||||
KITE_SURFING("kite surfing", R.string.activity_type_kite_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
LAND_SAILING("land sailing", R.string.activity_type_land_sailing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
MIXED_TYPE("mixed type", R.string.activity_type_mixed_type, ActivityIcon.ICON_UNKNOWN, true),
|
||||
MOTOR_BIKE("motor bike", R.string.activity_type_motor_bike, R.drawable.ic_activity_motorbike_24dp, true),
|
||||
MOTOR_BOATING("motor boating", R.string.activity_type_motor_boating, R.drawable.ic_activity_boat_24dp, true),
|
||||
MOUNTAIN_BIKING("mountain biking", R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true),
|
||||
MOUNTAIN_BIKING("mountain biking", List.of("mountain bike"), R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true),
|
||||
OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_off_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
OTHER("other", R.string.activity_type_other, ActivityIcon.ICON_UNKNOWN, true),
|
||||
PADDLING("paddling", R.string.activity_type_paddling, ActivityIcon.ICON_UNKNOWN, true),
|
||||
@@ -53,46 +54,57 @@ public enum ActivityType {
|
||||
RC_BOAT("RC boat", R.string.activity_type_rc_boat, R.drawable.ic_activity_boat_24dp, true),
|
||||
RC_HELICOPTER("RC helicopter", R.string.activity_type_rc_helicopter, ActivityIcon.ICON_UNKNOWN, true),
|
||||
RIDING("riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
ROAD_BIKING("road biking", R.string.activity_type_road_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
ROAD_BIKING("road biking", List.of("road bike"), R.string.activity_type_road_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
ROLLER_SKIING("roller skiing", R.string.activity_type_roller_skiing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
ROWING("rowing", R.string.activity_type_rowing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
RUNNING("running", R.string.activity_type_running, R.drawable.ic_activity_run_24dp, false),
|
||||
RUNNING("running", List.of("run", "treadmill"), R.string.activity_type_running, R.drawable.ic_activity_run_24dp, false),
|
||||
SAILING("sailing", R.string.activity_type_sailing, R.drawable.ic_activity_sailing_24dp, true),
|
||||
KICKSCOOTER("kickscooter", R.string.activity_type_kickscooter, R.drawable.ic_activity_scooter_24dp, true),
|
||||
SEAPLANE("seaplane", R.string.activity_type_seaplane, R.drawable.ic_activity_flight_24dp, true),
|
||||
SKATE_BOARDING("skateboarding", R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp, true),
|
||||
SKATE_BOARDING("skateboarding", List.of("skateboard"), R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp, true),
|
||||
SKATING("skating", R.string.activity_type_skating, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SKIING("skiing", R.string.activity_type_skiing, R.drawable.ic_activity_skiing_24dp, true),
|
||||
SKY_JUMPING("sky jumping", R.string.activity_type_sky_jumping, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SLED("sled", R.string.activity_type_sled, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SNOW_BOARDING("snowboarding", R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp, true),
|
||||
SNOW_BOARDING("snowboarding", List.of("snowboard"), R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp, true),
|
||||
SNOW_SHOEING("snow shoeing", R.string.activity_type_snow_shoeing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SPEED_WALKING("speed walking", R.string.activity_type_speed_walking, R.drawable.ic_activity_walk_24dp, false),
|
||||
STREET_RUNNING("street running", R.string.activity_type_street_running, R.drawable.ic_activity_run_24dp, false),
|
||||
SURFING("surfing", R.string.activity_type_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
TRACK_CYCLING("track cycling", R.string.activity_type_track_cycling, R.drawable.ic_activity_bike_24dp, true),
|
||||
TRACK_RUNNING("track running", R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||
TRACK_RUNNING("track running", List.of("track run"), R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||
TRAIL_HIKING("trail hiking", R.string.activity_type_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
TRAIL_RUNNING("trail running", R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||
TRAIL_RUNNING("trail running", List.of("trail run"), R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||
TRAIN("train", R.string.activity_type_train, ActivityIcon.ICON_UNKNOWN, true),
|
||||
ULTIMATE_FRISBEE("ultimate frisbee", R.string.activity_type_ultimate_frisbee, ActivityIcon.ICON_UNKNOWN, true),
|
||||
WAKEBOARDING("wakeboarding", R.string.activity_type_wakeboarding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
WALKING("walking", R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp, false),
|
||||
WALKING("walking", List.of("walk"), R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp, false),
|
||||
WATER_SKIING("water skiing", R.string.activity_type_water_skiing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
WIND_SURFING("wind surfing", R.string.activity_type_wind_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SWIMMING("swimming", R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp, false),
|
||||
SWIMMING_OPEN("swimming in open water", R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp, false),
|
||||
SWIMMING("swimming", List.of("swim"), R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp, false),
|
||||
SWIMMING_OPEN("swimming in open water", List.of("swimming openwater", "open water swimming"), R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp, false),
|
||||
WORKOUT("workout", R.string.activity_type_workout, R.drawable.ic_activity_workout_24dp, false),
|
||||
UNKNOWN("unknown", R.string.activity_type_unknown, ActivityIcon.ICON_UNKNOWN, true);
|
||||
|
||||
final String id;
|
||||
|
||||
/**
|
||||
* Spellings used by other applications that {@link #normalize(String)} cannot map onto {@link #id}.
|
||||
* Already normalized.
|
||||
*/
|
||||
final List<String> synonyms;
|
||||
|
||||
final int iconDrawableId;
|
||||
final boolean showSpeedPreferred;
|
||||
final int localizedStringId;
|
||||
|
||||
ActivityType(String id, int localizedStringId, int iconDrawableId, boolean showSpeedPreferred) {
|
||||
this(id, List.of(), localizedStringId, iconDrawableId, showSpeedPreferred);
|
||||
}
|
||||
|
||||
ActivityType(String id, List<String> synonyms, int localizedStringId, int iconDrawableId, boolean showSpeedPreferred) {
|
||||
this.id = id;
|
||||
this.synonyms = synonyms;
|
||||
this.localizedStringId = localizedStringId;
|
||||
this.iconDrawableId = iconDrawableId;
|
||||
this.showSpeedPreferred = showSpeedPreferred;
|
||||
@@ -121,12 +133,39 @@ public enum ActivityType {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lowercases and reduces "_", "-" and whitespace runs to a single space.
|
||||
* Lowercasing is locale-independent: under a Turkish locale the I of "RUNNING" would otherwise
|
||||
* lowercase to a dotless i and stop matching "running".
|
||||
*/
|
||||
@NonNull
|
||||
private static String normalize(String activityTypeId) {
|
||||
if (activityTypeId == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return activityTypeId.toLowerCase(Locale.ROOT)
|
||||
.replaceAll("[\\s_-]+", " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an activity type as written by another application; GPX's type element is free text.
|
||||
*/
|
||||
@NonNull
|
||||
public static ActivityType findBy(String activityTypeId) {
|
||||
return Arrays.stream(ActivityType.values()).filter(
|
||||
it -> it.id.equals(activityTypeId)
|
||||
).findFirst()
|
||||
.orElse(ActivityType.UNKNOWN);
|
||||
String normalized = normalize(activityTypeId);
|
||||
if (normalized.isEmpty()) {
|
||||
return ActivityType.UNKNOWN;
|
||||
}
|
||||
|
||||
return Arrays.stream(ActivityType.values())
|
||||
.filter(it -> normalize(it.id).equals(normalized))
|
||||
.findFirst()
|
||||
.orElseGet(() -> Arrays.stream(ActivityType.values())
|
||||
.filter(it -> it.synonyms.contains(normalized))
|
||||
.findFirst()
|
||||
.orElse(ActivityType.UNKNOWN));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -105,7 +105,7 @@ public class ExportActivity extends AbstractActivity {
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
// List of tracks to be exported.
|
||||
private ArrayList<ExportTask> exportTasks;
|
||||
private final ArrayList<ExportTask> exportTasks = new ArrayList<>();
|
||||
|
||||
private final LinkedBlockingQueue<PendingConflict> conflictsQueue = new LinkedBlockingQueue<>();
|
||||
private final Handler conflictsHandler = new Handler();
|
||||
@@ -183,7 +183,7 @@ public class ExportActivity extends AbstractActivity {
|
||||
trackExportTotalCount = savedInstanceState.getInt(BUNDLE_TOTAL_COUNT);
|
||||
directoryFiles = savedInstanceState.getStringArrayList(BUNDLE_DIRECTORY_FILES);
|
||||
trackErrors = savedInstanceState.getStringArrayList(BUNDLE_TRACK_ERRORS);
|
||||
exportTasks = new ArrayList<>(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS));
|
||||
exportTasks.addAll(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS));
|
||||
setProgress();
|
||||
nextExport(null);
|
||||
}
|
||||
@@ -226,7 +226,6 @@ public class ExportActivity extends AbstractActivity {
|
||||
return false;
|
||||
}
|
||||
|
||||
exportTasks = new ArrayList<>();
|
||||
if (allInOneFile) {
|
||||
String filename = "OpenTracks-Backup";
|
||||
exportTasks.add(new ExportTask(filename, trackFileFormat, tracks.stream().map(Track::id).toList()));
|
||||
|
||||
@@ -282,10 +282,11 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
Statistics trackStatistics = track.statistics();
|
||||
printWriter.println("<gpxtrkx:TrackStatsExtension>");
|
||||
printWriter.println("<gpxtrkx:Distance>" + trackStatistics.totalDistance().toM() + "</gpxtrkx:Distance>");
|
||||
printWriter.println("<gpxtrkx:TimerTime>" + trackStatistics.totalDuration().getSeconds() + "</gpxtrkx:TimerTime>");
|
||||
printWriter.println("<gpxtrkx:TotalElapsedTime>" + trackStatistics.totalDuration().getSeconds() + "</gpxtrkx:TotalElapsedTime>");
|
||||
printWriter.println("<gpxtrkx:MovingTime>" + trackStatistics.movingDuration().getSeconds() + "</gpxtrkx:MovingTime>");
|
||||
printWriter.println("<gpxtrkx:StoppedTime>" + trackStatistics.getStoppedTime().getSeconds() + "</gpxtrkx:StoppedTime>");
|
||||
printWriter.println("<gpxtrkx:MaxSpeed>" + trackStatistics.maxSpeed().toMPS() + "</gpxtrkx:MaxSpeed>");
|
||||
printWriter.println("<gpxtrkx:MovingSpeed>" + trackStatistics.getAverageMovingSpeed().toMPS() + "</gpxtrkx:MovingSpeed>");
|
||||
if (trackStatistics.altitudeGainLoss() != null) {
|
||||
printWriter.println("<gpxtrkx:Ascent>" + trackStatistics.altitudeGainLoss().gain_m() + "</gpxtrkx:Ascent>");
|
||||
printWriter.println("<gpxtrkx:Descent>" + trackStatistics.altitudeGainLoss().loss_m() + "</gpxtrkx:Descent>");
|
||||
|
||||
@@ -18,8 +18,11 @@ package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
@@ -71,28 +74,44 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
private static final String TAG_TRACK_POINT = "trkpt";
|
||||
private static final String TAG_TRACK_SEGMENT = "trkseg";
|
||||
private static final String TAG_TYPE = "type";
|
||||
private static final String TAG_TYPE_LOCALIZED = "opentracks:typeTranslated";
|
||||
private static final String TAG_MARKER = "wpt";
|
||||
private static final String TAG_ID = "opentracks:trackid";
|
||||
|
||||
private static final String ATTRIBUTE_LAT = "lat";
|
||||
private static final String ATTRIBUTE_LON = "lon";
|
||||
|
||||
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
|
||||
/**
|
||||
* Often speed is exported without the proper namespace.
|
||||
*/
|
||||
private static final String TAG_EXTENSION_SPEED_COMPAT = "speed";
|
||||
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
||||
private static final String TAG_EXTENSION_TEMPERATURE = "gpxtpx:atemp";
|
||||
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
||||
private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts";
|
||||
|
||||
private static final String TAG_EXTENSION_GAIN = "opentracks:gain";
|
||||
private static final String TAG_EXTENSION_LOSS = "opentracks:loss";
|
||||
private static final String TAG_EXTENSION_DISTANCE = "opentracks:distance";
|
||||
private static final String TAG_EXTENSION_ACCURACY_HORIZONTAL = "opentracks:accuracy_horizontal";
|
||||
private static final String TAG_EXTENSION_ACCURACY_VERTICAL = "opentracks:accuracy_vertical";
|
||||
/**
|
||||
* Garmin Trackpoint Extensions.
|
||||
*/
|
||||
private static final String NS_GARMIN_TRACKPOINT_EXTENSION_V1 = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1";
|
||||
private static final String NS_GARMIN_TRACKPOINT_EXTENSION_V2 = "http://www.garmin.com/xmlschemas/TrackPointExtension/v2";
|
||||
private static final String TAG_GARMIN_TPX_EXTENSION_SPEED = "speed";
|
||||
private static final String TAG_GARMIN_TPX_EXTENSION_HEARTRATE = "hr";
|
||||
private static final String TAG_GARMIN_TPX_EXTENSION_TEMPERATURE = "atemp";
|
||||
private static final String TAG_GARMIN_TPX_EXTENSION_CADENCE = "cad";
|
||||
|
||||
/**
|
||||
* Garmin Trackpoint Extensions.
|
||||
*/
|
||||
private static final String NS_GARMIN_POWER_EXTENSION_V1 = "http://www.garmin.com/xmlschemas/PowerExtension/v1";
|
||||
private static final String TAG_GARMIN_PWR_EXTENSION_POWER = "PowerInWatts";
|
||||
|
||||
/**
|
||||
* OpenTracks Extensions.
|
||||
*/
|
||||
private static final String NS_OPENTRACKS = "http://opentracksapp.com/xmlschemas/v1";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_TRACK_ID = "trackid";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_TYPE_LOCALIZED = "typeTranslated";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_GAIN = "gain";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_LOSS = "loss";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_DISTANCE = "distance";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_ACCURACY_HORIZONTAL = "accuracy_horizontal";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_ACCURACY_VERTICAL = "accuracy_vertical";
|
||||
|
||||
private Locator locator;
|
||||
|
||||
private final Context context;
|
||||
@@ -142,8 +161,8 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes) {
|
||||
switch (tag) {
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
switch (qName) {
|
||||
case TAG_MARKER:
|
||||
onMarkerStart(attributes);
|
||||
break;
|
||||
@@ -165,105 +184,128 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) {
|
||||
switch (tag) {
|
||||
case TAG_GPX -> onFileEnd();
|
||||
case TAG_MARKER -> onMarkerEnd();
|
||||
case TAG_TRACK -> {
|
||||
if (activityTypeLocalized == null) {
|
||||
// Backward compatibility: up v4.9.1 as <type> contained localized content.
|
||||
activityTypeLocalized = activityType;
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
if (NS_GARMIN_TRACKPOINT_EXTENSION_V1.equals(uri) || NS_GARMIN_TRACKPOINT_EXTENSION_V2.equals(uri)) {
|
||||
switch (localName) {
|
||||
case TAG_GARMIN_TPX_EXTENSION_SPEED -> {
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
}
|
||||
}
|
||||
trackImporter.setTrackData(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset);
|
||||
zoneOffset = null;
|
||||
}
|
||||
case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
|
||||
case TAG_TRACK_POINT -> currentSegment.add(createTrackPoint());
|
||||
case TAG_NAME -> {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
case TAG_GARMIN_TPX_EXTENSION_HEARTRATE -> {
|
||||
if (content != null) {
|
||||
heartrate = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_DESCRIPTION -> {
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
case TAG_GARMIN_TPX_EXTENSION_TEMPERATURE -> {
|
||||
if (content != null) {
|
||||
temperature = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_TYPE -> { //Track or Marker/WPT
|
||||
if (content != null) {
|
||||
// In older version this might be localized content.
|
||||
activityType = content.trim();
|
||||
markerType = content.trim();
|
||||
case TAG_GARMIN_TPX_EXTENSION_CADENCE -> {
|
||||
if (content != null) {
|
||||
cadence = content.trim();
|
||||
}
|
||||
}
|
||||
default -> Log.w(TAG, "Unknown Garmin trackpoint extension tag " + localName);
|
||||
}
|
||||
case TAG_TYPE_LOCALIZED -> {
|
||||
if (content != null) {
|
||||
activityTypeLocalized = content.trim();
|
||||
} else if (NS_GARMIN_POWER_EXTENSION_V1.equals(uri)) {
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
switch (localName) {
|
||||
case TAG_GARMIN_PWR_EXTENSION_POWER -> {
|
||||
if (content != null) {
|
||||
power = content.trim();
|
||||
}
|
||||
}
|
||||
default -> Log.w(TAG, "Unknown Garmin power extension tag " + localName);
|
||||
}
|
||||
case TAG_TIME -> {
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
} else if (NS_OPENTRACKS.equals(uri)) {
|
||||
switch (localName) {
|
||||
case TAG_OPENTRACKS_EXTENSION_TRACK_ID -> {
|
||||
if (content != null) {
|
||||
uuid = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_ALTITUDE -> {
|
||||
if (content != null) {
|
||||
altitude = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_TYPE_LOCALIZED -> {
|
||||
if (content != null) {
|
||||
activityTypeLocalized = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_SPEED, TAG_EXTENSION_SPEED_COMPAT -> {
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_GAIN -> {
|
||||
if (content != null) {
|
||||
gain = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_HEARTRATE -> {
|
||||
if (content != null) {
|
||||
heartrate = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_LOSS -> {
|
||||
if (content != null) {
|
||||
loss = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_TEMPERATURE -> {
|
||||
if (content != null) {
|
||||
temperature = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_DISTANCE -> {
|
||||
if (content != null) {
|
||||
sensorDistance = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_CADENCE -> {
|
||||
if (content != null) {
|
||||
cadence = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_ACCURACY_HORIZONTAL -> {
|
||||
if (content != null) {
|
||||
accuracyHorizontal = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_POWER -> {
|
||||
if (content != null) {
|
||||
power = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_ACCURACY_VERTICAL -> {
|
||||
if (content != null) {
|
||||
accuracyVertical = content.trim();
|
||||
}
|
||||
}
|
||||
default -> Log.w(TAG, "Unknown OpenTracks extension tag " + localName);
|
||||
}
|
||||
case TAG_ID -> {
|
||||
if (content != null) {
|
||||
uuid = content.trim();
|
||||
} else {
|
||||
switch (qName) {
|
||||
case TAG_GPX -> onFileEnd();
|
||||
case TAG_MARKER -> onMarkerEnd();
|
||||
case TAG_TRACK -> {
|
||||
if (activityTypeLocalized == null) {
|
||||
// Backward compatibility: up v4.9.1 as <type> contained localized content.
|
||||
activityTypeLocalized = activityType;
|
||||
}
|
||||
trackImporter.setTrackData(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset);
|
||||
zoneOffset = null;
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_GAIN -> {
|
||||
if (content != null) {
|
||||
gain = content.trim();
|
||||
case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
|
||||
case TAG_TRACK_POINT -> {
|
||||
TrackPoint trackPoint = createTrackPoint();
|
||||
if (trackPoint != null) currentSegment.add(trackPoint);
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_LOSS -> {
|
||||
if (content != null) {
|
||||
loss = content.trim();
|
||||
case TAG_NAME -> {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_DISTANCE -> {
|
||||
if (content != null) {
|
||||
sensorDistance = content.trim();
|
||||
case TAG_DESCRIPTION -> {
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_ACCURACY_HORIZONTAL -> {
|
||||
if (content != null) {
|
||||
accuracyHorizontal = content.trim();
|
||||
case TAG_TYPE -> { //Track or Marker/WPT
|
||||
if (content != null) {
|
||||
// In older version this might be localized content.
|
||||
activityType = content.trim();
|
||||
markerType = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_ACCURACY_VERTICAL -> {
|
||||
if (content != null) {
|
||||
accuracyVertical = content.trim();
|
||||
case TAG_TIME -> {
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
}
|
||||
}
|
||||
case TAG_ALTITUDE -> {
|
||||
if (content != null) {
|
||||
altitude = content.trim();
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_SPEED_COMPAT -> {
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,8 +326,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
currentSegment.clear();
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private TrackPoint createTrackPoint() throws ParsingException {
|
||||
if (TextUtils.isEmpty(time)) return null;
|
||||
|
||||
OffsetDateTime parsedTime;
|
||||
try {
|
||||
parsedTime = StringUtils.parseTime(time);
|
||||
@@ -468,7 +512,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = createTrackPoint();
|
||||
|
||||
if (trackPoint == null) {
|
||||
Log.w(TAG, "Marker with no TrackPoint");
|
||||
return;
|
||||
}
|
||||
if (!trackPoint.position().hasLocation()) {
|
||||
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.position());
|
||||
return;
|
||||
|
||||
@@ -146,8 +146,8 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||
switch (tag) {
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
|
||||
switch (qName) {
|
||||
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
||||
// Note that a track is contained in a Placemark, calling onMarkerStart will clear various track variables like name, typeLocalized, and description.
|
||||
onMarkerStart();
|
||||
@@ -168,8 +168,8 @@ public class KMLTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||
switch (tag) {
|
||||
public void endElement(String uri, String localName, String qName) throws SAXException {
|
||||
switch (qName) {
|
||||
case TAG_KML -> onFileEnd();
|
||||
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
||||
// Note that a track is contained in a Placemark, calling onMarkerEnd is save since markerType is not set for a track.
|
||||
|
||||
+13
-5
@@ -5,7 +5,10 @@ import android.content.Context;
|
||||
import androidx.health.connect.client.HealthConnectClient;
|
||||
import androidx.health.connect.client.PermissionController;
|
||||
import androidx.health.connect.client.permission.HealthPermission;
|
||||
import androidx.health.connect.client.records.DistanceRecord;
|
||||
import androidx.health.connect.client.records.ElevationGainedRecord;
|
||||
import androidx.health.connect.client.records.ExerciseSessionRecord;
|
||||
import androidx.health.connect.client.records.SpeedRecord;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.ExistingWorkPolicy;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
@@ -27,11 +30,13 @@ import kotlinx.coroutines.BuildersKt;
|
||||
*/
|
||||
public class HealthConnectUtils {
|
||||
|
||||
static final String WRITE_EXERCISE_PERMISSION = HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(ExerciseSessionRecord.class));
|
||||
static final String WRITE_EXERCISE_ROUTE_PERMISSION = HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE;
|
||||
|
||||
//TODO Should be package private
|
||||
public static final Set<String> PERMISSIONS = Set.of(WRITE_EXERCISE_PERMISSION, WRITE_EXERCISE_ROUTE_PERMISSION);
|
||||
public static final Set<String> PERMISSIONS = Set.of(
|
||||
HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(ExerciseSessionRecord.class)),
|
||||
HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(DistanceRecord.class)),
|
||||
HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(ElevationGainedRecord.class)),
|
||||
HealthPermission.getWritePermission(JvmClassMappingKt.getKotlinClass(SpeedRecord.class)),
|
||||
HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE
|
||||
);
|
||||
|
||||
private HealthConnectUtils() {
|
||||
}
|
||||
@@ -41,6 +46,9 @@ public class HealthConnectUtils {
|
||||
}
|
||||
|
||||
public static Set<String> getGrantedPermissions(Context context) {
|
||||
if (!isAvailable(context))
|
||||
return Set.of();
|
||||
|
||||
try {
|
||||
HealthConnectClient client = HealthConnectClient.getOrCreate(context);
|
||||
PermissionController controller = client.getPermissionController();
|
||||
|
||||
+89
-22
@@ -6,16 +6,22 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.health.connect.client.HealthConnectClient;
|
||||
import androidx.health.connect.client.records.DistanceRecord;
|
||||
import androidx.health.connect.client.records.ElevationGainedRecord;
|
||||
import androidx.health.connect.client.records.ExerciseRoute;
|
||||
import androidx.health.connect.client.records.ExerciseSessionRecord;
|
||||
import androidx.health.connect.client.records.Record;
|
||||
import androidx.health.connect.client.records.SpeedRecord;
|
||||
import androidx.health.connect.client.records.metadata.Device;
|
||||
import androidx.health.connect.client.records.metadata.Metadata;
|
||||
import androidx.health.connect.client.units.Length;
|
||||
import androidx.health.connect.client.units.Velocity;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
@@ -111,27 +117,65 @@ public class HealthConnectWorker extends Worker {
|
||||
throw new RuntimeException("Track not found: " + trackId);
|
||||
}
|
||||
|
||||
ExerciseRoute exerciseRoute = createExerciseRoute(contentProviderUtils, trackId);
|
||||
|
||||
HealthConnectClient client = HealthConnectClient.getOrCreate(context);
|
||||
Metadata metadata = Metadata.activelyRecorded(
|
||||
new Device(Device.TYPE_PHONE, Build.MANUFACTURER, Build.MODEL),
|
||||
track.uuid().toString()
|
||||
);
|
||||
|
||||
ExerciseSessionRecord record = new ExerciseSessionRecord(
|
||||
List<Record> records = new ArrayList<>();
|
||||
|
||||
ExerciseData exerciseData = createExerciseData(contentProviderUtils, trackId);
|
||||
|
||||
//Avoid bug in HealthConnect: data must be within ExerciseSessionRecord.startTime and ExerciseSessionRecord.stopTime
|
||||
List<ExerciseRoute.Location> locations = exerciseData.locations.stream()
|
||||
.filter(i -> track.startTime().toInstant().isBefore(i.getTime()) && track.stopTime().toInstant().isAfter(i.getTime()))
|
||||
.toList();
|
||||
|
||||
records.add(new ExerciseSessionRecord(
|
||||
track.startTime().toInstant(),
|
||||
track.startTime().getOffset(),
|
||||
track.stopTime().toInstant(),
|
||||
track.stopTime().getOffset(),
|
||||
Metadata.activelyRecorded(
|
||||
new Device(Device.TYPE_PHONE, Build.MANUFACTURER, Build.MODEL),
|
||||
track.uuid().toString()
|
||||
),
|
||||
metadata,
|
||||
toExerciseType(track.activityType()),
|
||||
track.name(),
|
||||
track.description(),
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
exerciseRoute);
|
||||
new ExerciseRoute(locations)));
|
||||
|
||||
records.add(new DistanceRecord(
|
||||
track.startTime().toInstant(),
|
||||
track.startTime().getOffset(),
|
||||
track.stopTime().toInstant(),
|
||||
track.stopTime().getOffset(),
|
||||
Length.meters(track.statistics().totalDistance().toM()),
|
||||
metadata
|
||||
));
|
||||
|
||||
if (track.statistics().altitudeGainLoss() != null) {
|
||||
records.add(new ElevationGainedRecord(
|
||||
track.startTime().toInstant(),
|
||||
track.startTime().getOffset(),
|
||||
track.stopTime().toInstant(),
|
||||
track.stopTime().getOffset(),
|
||||
Length.meters(track.statistics().altitudeGainLoss().gain_m()),
|
||||
metadata
|
||||
));
|
||||
}
|
||||
|
||||
if (!exerciseData.speeds.isEmpty()) {
|
||||
records.add(new SpeedRecord(
|
||||
track.startTime().toInstant(),
|
||||
track.startTime().getOffset(),
|
||||
track.stopTime().toInstant(),
|
||||
track.stopTime().getOffset(),
|
||||
exerciseData.speeds,
|
||||
metadata
|
||||
));
|
||||
}
|
||||
|
||||
List<ExerciseSessionRecord> records = Collections.singletonList(record);
|
||||
Object ignored = BuildersKt.runBlocking(
|
||||
kotlin.coroutines.EmptyCoroutineContext.INSTANCE,
|
||||
(Function2) (scope, cont) -> client.insertRecords(records, (Continuation) cont));
|
||||
@@ -140,30 +184,53 @@ public class HealthConnectWorker extends Worker {
|
||||
return new ExportResult(track.name());
|
||||
}
|
||||
|
||||
private static ExerciseRoute createExerciseRoute(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
|
||||
List<ExerciseRoute.Location> route = new ArrayList<>();
|
||||
private static ExerciseData createExerciseData(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
|
||||
List<ExerciseRoute.Location> locations = new LinkedList<>();
|
||||
List<SpeedRecord.Sample> speeds = new LinkedList<>();
|
||||
|
||||
try (TrackPointIterator iterator = contentProviderUtils.getTrackPointIterator(trackId, null)) {
|
||||
while (iterator.hasNext()) {
|
||||
while (iterator.hasNext()) { //TODO iterator.iterator.forEachRemaining()
|
||||
TrackPoint trackPoint = iterator.next();
|
||||
if (trackPoint.type() != TrackPoint.Type.TRACKPOINT) continue;
|
||||
if (trackPoint.type() != TrackPoint.Type.TRACKPOINT) continue; //TODO Include SEGMENT_START_AUTOMATIC?
|
||||
|
||||
Position position = trackPoint.position();
|
||||
|
||||
route.add(new ExerciseRoute.Location(
|
||||
trackPoint.getTime(),
|
||||
position.latitude(),
|
||||
position.longitude(),
|
||||
position.horizontalAccuracy() != null ? Length.meters(position.horizontalAccuracy().toM()) : null,
|
||||
position.verticalAccuracy() != null ? Length.meters(position.verticalAccuracy().toM()) : null,
|
||||
position.altitude() != null ? Length.meters(position.altitude().toM()) : null)
|
||||
);
|
||||
if (position.hasLocation()) {
|
||||
//ExcerciseRoute does not allow duplicated timestamps.
|
||||
if (locations.stream().noneMatch(location -> location.getTime().equals(position.time()))) {
|
||||
locations.add(new ExerciseRoute.Location(
|
||||
trackPoint.getTime(),
|
||||
position.latitude(),
|
||||
position.longitude(),
|
||||
position.horizontalAccuracy() != null ? Length.meters(position.horizontalAccuracy().toM()) : null,
|
||||
position.verticalAccuracy() != null ? Length.meters(position.verticalAccuracy().toM()) : null,
|
||||
position.altitude() != null ? Length.meters(position.altitude().toM()) : null)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (position.hasSpeed()) {
|
||||
speeds.add(new SpeedRecord.Sample(
|
||||
trackPoint.getTime(),
|
||||
Velocity.metersPerSecond(position.speed().toMPS())
|
||||
));
|
||||
}
|
||||
|
||||
//TODO Cadence
|
||||
//TODO Power
|
||||
//TODO Heartrate
|
||||
}
|
||||
}
|
||||
|
||||
return new ExerciseRoute(route);
|
||||
return new ExerciseData(locations, speeds);
|
||||
}
|
||||
|
||||
private record ExerciseData(
|
||||
List<ExerciseRoute.Location> locations,
|
||||
List<SpeedRecord.Sample> speeds
|
||||
) {}
|
||||
|
||||
|
||||
private static int toExerciseType(ActivityType activityType) {
|
||||
return switch (activityType) {
|
||||
case RUNNING, STREET_RUNNING, TRAIL_RUNNING, TRACK_RUNNING -> ExerciseSessionRecord.EXERCISE_TYPE_RUNNING;
|
||||
|
||||
@@ -45,8 +45,9 @@ public class GpsHandler extends SensorHandler<Location, Position> {
|
||||
gpsStatusManager.onMinSamplingIntervalChanged(PreferencesUtils.getMinSamplingInterval());
|
||||
}
|
||||
|
||||
//TODO
|
||||
connect();
|
||||
if (driver != null) {
|
||||
driver.updateDriverParameters(getContext(), sensorManager.getHandler());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ public abstract class SensorHandler<AggregatorInput, AggregatorOutput> implement
|
||||
|
||||
private static final String TAG = SensorHandler.class.getSimpleName();
|
||||
|
||||
private final SensorManager sensorManager;
|
||||
|
||||
@VisibleForTesting
|
||||
public Driver driver;
|
||||
|
||||
protected final SensorManager sensorManager;
|
||||
|
||||
protected Aggregator<AggregatorInput, AggregatorOutput> aggregator;
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -11,5 +11,9 @@ public interface Driver {
|
||||
//TODO Remove from interface?
|
||||
boolean isConnected();
|
||||
|
||||
default void updateDriverParameters(Context context, Handler handler) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
void disconnect();
|
||||
}
|
||||
|
||||
@@ -57,16 +57,11 @@ public class GpsInternal implements Driver {
|
||||
return;
|
||||
}
|
||||
|
||||
LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(PreferencesUtils.getMinSamplingInterval().toMillis())
|
||||
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
||||
.setMaxUpdateDelayMillis(0)
|
||||
.build();
|
||||
|
||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||
try {
|
||||
Log.i(TAG, "Register for location updates " + context);
|
||||
Log.i(TAG, "Register for location updates");
|
||||
driverObserver.onConnected(null, null);
|
||||
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, locationRequest, handler::post, locationListenerCompat);
|
||||
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, createLocationRequest(), handler::post, locationListenerCompat);
|
||||
return;
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Could not register location listener; permissions not granted.", e);
|
||||
@@ -76,6 +71,16 @@ public class GpsInternal implements Driver {
|
||||
driverObserver.onDisconnected();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"MissingPermission"})
|
||||
@Override
|
||||
public void updateDriverParameters(Context context, Handler handler) {
|
||||
if (!isConnected()) return;
|
||||
|
||||
Log.i(TAG, "Update for location updates");
|
||||
driverObserver.onConnected(null, null);
|
||||
LocationManagerCompat.requestLocationUpdates(locationManager, LOCATION_PROVIDER, createLocationRequest(), handler::post, locationListenerCompat);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"MissingPermission"})
|
||||
@Override
|
||||
public void disconnect() {
|
||||
@@ -87,9 +92,15 @@ public class GpsInternal implements Driver {
|
||||
driverObserver.onDisconnected();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return locationManager != null;
|
||||
}
|
||||
|
||||
private LocationRequestCompat createLocationRequest() {
|
||||
return new LocationRequestCompat.Builder(PreferencesUtils.getMinSamplingInterval().toMillis())
|
||||
.setQuality(LocationRequestCompat.QUALITY_HIGH_ACCURACY)
|
||||
.setMaxUpdateDelayMillis(0)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +172,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
}
|
||||
|
||||
synchronized void endCurrentTrack() {
|
||||
handler.removeCallbacks(ON_IDLE);
|
||||
TrackPoint segmentEnd = trackPointCreator.createSegmentEnd();
|
||||
insertTrackPoint(segmentEnd, true);
|
||||
|
||||
@@ -188,6 +189,10 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
}
|
||||
|
||||
RecordingData getDataForUI() {
|
||||
if (trackId == null) {
|
||||
throw new RuntimeException("Requesting data if not recording should not be done.");
|
||||
}
|
||||
|
||||
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPointUIWithSpeed, lastTrackPointUIWithAltitude, lastStoredTrackPointWithLocation);
|
||||
TrackPoint trackPoint = current.first;
|
||||
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater, trackPoint);
|
||||
@@ -196,8 +201,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId); //Get copy TODO: Avoid re-loading data from DB
|
||||
if (track == null) {
|
||||
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
|
||||
return null;
|
||||
throw new RuntimeException("Data could not be loaded for TrackId " + trackId);
|
||||
}
|
||||
|
||||
TrackBuilder trackBuilder = new TrackBuilder(track);
|
||||
|
||||
@@ -27,11 +27,6 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
|
||||
setExportTrackFileFormatOptions();
|
||||
setExportDirectorySummary();
|
||||
setFilenameTemplate();
|
||||
|
||||
findPreference("healthConnectPermissions").setSummaryProvider(preference -> {
|
||||
//TODO This should be localized and prefixes trimmed.
|
||||
return String.join(",", HealthConnectUtils.getGrantedPermissions(getActivity()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -73,7 +73,6 @@ public class PreferencesUtils {
|
||||
private static SharedPreferences sharedPreferences;
|
||||
|
||||
private static Resources resources;
|
||||
private static final long LONG_UNSET = Long.MIN_VALUE;
|
||||
|
||||
/**
|
||||
* Must be called during application startup.
|
||||
@@ -560,6 +559,7 @@ public class PreferencesUtils {
|
||||
String[] entries = new String[entryValues.length];
|
||||
|
||||
final int maxRecordingDistanceDefault = Integer.parseInt(resources.getString(R.string.max_recording_distance_default));
|
||||
final int maxRecordingDistanceOff = Integer.parseInt(resources.getString(R.string.max_recording_distance_off));
|
||||
UnitSystem unitSystem = getUnitSystem();
|
||||
|
||||
DistanceFormatter formatter = DistanceFormatter.Builder()
|
||||
@@ -576,6 +576,9 @@ public class PreferencesUtils {
|
||||
case METRIC, IMPERIAL_METER -> {
|
||||
if (value == maxRecordingDistanceDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
||||
}
|
||||
if (value == maxRecordingDistanceOff) {
|
||||
entries[i] = resources.getString(R.string.value_off);
|
||||
} else {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
@@ -583,6 +586,9 @@ public class PreferencesUtils {
|
||||
case IMPERIAL_FEET, NAUTICAL_IMPERIAL -> {
|
||||
if (value == maxRecordingDistanceDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
}
|
||||
if (value == maxRecordingDistanceOff) {
|
||||
entries[i] = resources.getString(R.string.value_off);
|
||||
} else {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user