forked from upstream-mirrors/OpenTracks
IntervalsFragment doesn't use TrackDataHub anymore. This improve intervals performance. Fixes #808.
This commit is contained in:
@@ -4,7 +4,6 @@ package de.dennisguse.opentracks;
|
|||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import java.util.List;
|
|||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
|
||||||
public class TestDataUtil {
|
public class TestDataUtil {
|
||||||
@@ -166,4 +167,20 @@ public class TestDataUtil {
|
|||||||
}
|
}
|
||||||
return trackPoints;
|
return trackPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Pair<Track.Id, TrackStatistics> buildTrackWithTrackPoints(ContentProviderUtils contentProviderUtils, int numberOfPoints) {
|
||||||
|
Track dummyTrack = new Track();
|
||||||
|
dummyTrack.setId(new Track.Id(System.currentTimeMillis()));
|
||||||
|
dummyTrack.setName("Dummy Track");
|
||||||
|
contentProviderUtils.insertTrack(dummyTrack);
|
||||||
|
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||||
|
for (int i = 0; i < numberOfPoints; i++) {
|
||||||
|
TrackPoint tp = TestDataUtil.createTrackPoint(i);
|
||||||
|
contentProviderUtils.insertTrackPoint(tp, dummyTrack.getId());
|
||||||
|
trackStatisticsUpdater.addTrackPoint(tp, Distance.of(0));
|
||||||
|
}
|
||||||
|
dummyTrack.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||||
|
contentProviderUtils.updateTrack(dummyTrack);
|
||||||
|
return new Pair<>(dummyTrack.getId(), trackStatisticsUpdater.getTrackStatistics());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||||
|
|
||||||
@@ -25,6 +27,12 @@ import static org.junit.Assert.assertEquals;
|
|||||||
public class AnnouncementUtilsTest {
|
public class AnnouncementUtilsTest {
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnouncement_metric_speed() {
|
public void getAnnouncement_metric_speed() {
|
||||||
@@ -44,22 +52,31 @@ public class AnnouncementUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnouncement_withInterval_metric_speed() {
|
public void getAnnouncement_withInterval_metric_speed() {
|
||||||
TrackStatistics stats = new TrackStatistics();
|
// given
|
||||||
stats.setTotalDistance(Distance.of(20000));
|
int numberOfPoints = 1000;
|
||||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
||||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
Track.Id trackId = trackWithStats.first;
|
||||||
stats.setMaxSpeed(Speed.of(100));
|
TrackStatistics stats = trackWithStats.second;
|
||||||
stats.setTotalAltitudeGain(6000f);
|
IntervalStatistics.Interval lastInterval;
|
||||||
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
assertEquals(trackPointIterator.getCount(), numberOfPoints);
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000), Distance.of(0));
|
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0));
|
||||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
|
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour Lap speed of 51.2 kilometers per hour", announcement);
|
assertEquals(
|
||||||
|
"OpenTracks total distance " +
|
||||||
|
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
|
||||||
|
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
|
||||||
|
StringUtils.getSpeedParts(context, stats.getAverageMovingSpeed(), true, true).first +
|
||||||
|
" kilometers per hour Lap speed of " +
|
||||||
|
StringUtils.getSpeedParts(context, lastInterval.getSpeed(), true, true).first +
|
||||||
|
" kilometers per hour",
|
||||||
|
announcement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -80,22 +97,31 @@ public class AnnouncementUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnouncement_withInterval_metric_pace() {
|
public void getAnnouncement_withInterval_metric_pace() {
|
||||||
TrackStatistics stats = new TrackStatistics();
|
// given
|
||||||
stats.setTotalDistance(Distance.of(20000));
|
int numberOfPoints = 1000;
|
||||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
||||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
Track.Id trackId = trackWithStats.first;
|
||||||
stats.setMaxSpeed(Speed.of(100));
|
TrackStatistics stats = trackWithStats.second;
|
||||||
stats.setTotalAltitudeGain(6000f);
|
IntervalStatistics.Interval lastInterval;
|
||||||
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
assertEquals(trackPointIterator.getCount(), numberOfPoints);
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0));
|
||||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval);
|
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer Lap time of 1 minute 10 seconds per kilometer", announcement);
|
assertEquals(
|
||||||
|
"OpenTracks total distance " +
|
||||||
|
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
|
||||||
|
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
|
||||||
|
buildAndGetTimeText(stats.getAverageMovingSpeed().toPace(true), true) +
|
||||||
|
" per kilometer Lap time of " +
|
||||||
|
buildAndGetTimeText(lastInterval.getSpeed().toPace(true), true) +
|
||||||
|
" per kilometer",
|
||||||
|
announcement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -116,22 +142,31 @@ public class AnnouncementUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnouncement_withInterval_imperial_speed() {
|
public void getAnnouncement_withInterval_imperial_speed() {
|
||||||
TrackStatistics stats = new TrackStatistics();
|
// given
|
||||||
stats.setTotalDistance(Distance.of(20000));
|
int numberOfPoints = 1000;
|
||||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
||||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
Track.Id trackId = trackWithStats.first;
|
||||||
stats.setMaxSpeed(Speed.of(100));
|
TrackStatistics stats = trackWithStats.second;
|
||||||
stats.setTotalAltitudeGain(6000f);
|
IntervalStatistics.Interval lastInterval;
|
||||||
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
assertEquals(trackPointIterator.getCount(), numberOfPoints);
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0));
|
||||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval);
|
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour Lap speed of 31.8 miles per hour", announcement);
|
assertEquals(
|
||||||
|
"OpenTracks total distance " +
|
||||||
|
StringUtils.getDistanceParts(context, stats.getTotalDistance(), false).first +
|
||||||
|
" miles in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
|
||||||
|
StringUtils.getSpeedParts(context, stats.getAverageMovingSpeed(), false, true).first +
|
||||||
|
" miles per hour Lap speed of " +
|
||||||
|
StringUtils.getSpeedParts(context, lastInterval.getSpeed(), false, true).first +
|
||||||
|
" miles per hour",
|
||||||
|
announcement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -152,21 +187,60 @@ public class AnnouncementUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnouncement_withInterval_imperial_pace() {
|
public void getAnnouncement_withInterval_imperial_pace() {
|
||||||
TrackStatistics stats = new TrackStatistics();
|
// given
|
||||||
stats.setTotalDistance(Distance.of(20000));
|
int numberOfPoints = 1000;
|
||||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
||||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
Track.Id trackId = trackWithStats.first;
|
||||||
stats.setMaxSpeed(Speed.of(100));
|
TrackStatistics stats = trackWithStats.second;
|
||||||
stats.setTotalAltitudeGain(6000f);
|
IntervalStatistics.Interval lastInterval;
|
||||||
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
assertEquals(trackPointIterator.getCount(), numberOfPoints);
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(1000), Distance.of(0));
|
||||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval);
|
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile Lap time of 1 minute 53 seconds per mile", announcement);
|
//assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile Lap time of 1 minute 53 seconds per mile", announcement);
|
||||||
|
assertEquals(
|
||||||
|
"OpenTracks total distance " +
|
||||||
|
StringUtils.getDistanceParts(context, stats.getTotalDistance(), false).first +
|
||||||
|
" miles in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
|
||||||
|
buildAndGetTimeText(stats.getAverageMovingSpeed().toPace(false), true) +
|
||||||
|
" per mile Lap time of " +
|
||||||
|
buildAndGetTimeText(lastInterval.getSpeed().toPace(false), true) +
|
||||||
|
" per mile",
|
||||||
|
announcement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an returns the text representing the duration's time.
|
||||||
|
*
|
||||||
|
* @param duration Duration object.
|
||||||
|
* @param showFromMinutes show minutes tough it's 0.
|
||||||
|
* @return text representing the duratin's time.
|
||||||
|
*/
|
||||||
|
private String buildAndGetTimeText(Duration duration, boolean showFromMinutes) {
|
||||||
|
long hours = Math.abs(duration.getSeconds()) / 3600;
|
||||||
|
long minutes = (Math.abs(duration.getSeconds()) % 3600) / 60;
|
||||||
|
long seconds = Math.abs(duration.getSeconds()) % 60;
|
||||||
|
String hUnit = hours > 1 || hours == 0 ? "hours" : "hour";
|
||||||
|
String mUnit = minutes > 1 || minutes == 0 ? "minutes" : "minute";
|
||||||
|
String sUnit = seconds > 1 || seconds == 0 ? "seconds" : "second";
|
||||||
|
|
||||||
|
String res = hours > 0 ? hours + " " + hUnit : "";
|
||||||
|
if (hours > 0) {
|
||||||
|
res += minutes > 0 || showFromMinutes ? " " + minutes + " " + mUnit : "";
|
||||||
|
} else {
|
||||||
|
res += minutes > 0 || showFromMinutes ? minutes + " " + mUnit : "";
|
||||||
|
}
|
||||||
|
if (hours > 0 || minutes > 0 || showFromMinutes) {
|
||||||
|
res += " " + seconds + " " + sUnit;
|
||||||
|
} else {
|
||||||
|
res += seconds + " " + sUnit;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+29
-20
@@ -1,5 +1,11 @@
|
|||||||
package de.dennisguse.opentracks.viewmodels;
|
package de.dennisguse.opentracks.viewmodels;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
@@ -10,6 +16,8 @@ import de.dennisguse.opentracks.content.data.Distance;
|
|||||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
@@ -21,8 +29,12 @@ public class IntervalStatisticsTest {
|
|||||||
|
|
||||||
private static final String TAG = IntervalStatisticsTest.class.getSimpleName();
|
private static final String TAG = IntervalStatisticsTest.class.getSimpleName();
|
||||||
|
|
||||||
private List<TrackPoint> buildTrackPoints(int numberOfTrackPoints) {
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).second;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
contentProviderUtils = new ContentProviderUtils(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
|
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
|
||||||
@@ -41,12 +53,10 @@ public class IntervalStatisticsTest {
|
|||||||
// With 50 points and interval distance of 1000m.
|
// With 50 points and interval distance of 1000m.
|
||||||
|
|
||||||
// given
|
// given
|
||||||
List<TrackPoint> trackPoints = buildTrackPoints(50);
|
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
|
||||||
float distanceInterval = 1000f;
|
float distanceInterval = 1000f;
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
whenAndThen(50, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,12 +67,10 @@ public class IntervalStatisticsTest {
|
|||||||
// With 200 points and interval distance of 1000m.
|
// With 200 points and interval distance of 1000m.
|
||||||
|
|
||||||
// given
|
// given
|
||||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
|
||||||
float distanceInterval = 1000f;
|
float distanceInterval = 1000f;
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
whenAndThen(200, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,12 +81,10 @@ public class IntervalStatisticsTest {
|
|||||||
// With 200 points and interval distance of 3000m.
|
// With 200 points and interval distance of 3000m.
|
||||||
|
|
||||||
// given
|
// given
|
||||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
|
||||||
float distanceInterval = 3000f;
|
float distanceInterval = 3000f;
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
whenAndThen(3000, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,12 +95,10 @@ public class IntervalStatisticsTest {
|
|||||||
// With 1000 points and interval distance of 3000m.
|
// With 1000 points and interval distance of 3000m.
|
||||||
|
|
||||||
// given
|
// given
|
||||||
List<TrackPoint> trackPoints = buildTrackPoints(1000);
|
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
|
||||||
float distanceInterval = 3000f;
|
float distanceInterval = 3000f;
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
whenAndThen(1000, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,16 +109,21 @@ public class IntervalStatisticsTest {
|
|||||||
// With 10000 points and interval distance of 1000m.
|
// With 10000 points and interval distance of 1000m.
|
||||||
|
|
||||||
// given
|
// given
|
||||||
List<TrackPoint> trackPoints = buildTrackPoints(10000);
|
|
||||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
|
||||||
float distanceInterval = 1000f;
|
float distanceInterval = 1000f;
|
||||||
|
|
||||||
// when and then
|
// when and then
|
||||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
whenAndThen(10000, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void whenAndThen(List<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval) {
|
private void whenAndThen(int numberOfPoints, float distanceInterval) {
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(distanceInterval), Distance.of(0));
|
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval), Distance.of(0));
|
||||||
|
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
||||||
|
Track.Id trackId = trackWithStats.first;
|
||||||
|
TrackStatistics trackStatistics = trackWithStats.second;
|
||||||
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||||
|
assertEquals(trackPointIterator.getCount(), numberOfPoints);
|
||||||
|
intervalStatistics.addTrackPoints(trackPointIterator);
|
||||||
|
}
|
||||||
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
|
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
|
||||||
Distance totalDistance = Distance.of(0);
|
Distance totalDistance = Distance.of(0);
|
||||||
float totalTime = 0L;
|
float totalTime = 0L;
|
||||||
@@ -129,7 +138,7 @@ public class IntervalStatisticsTest {
|
|||||||
assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance.toM(), 0.01);
|
assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance.toM(), 0.01);
|
||||||
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1);
|
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1);
|
||||||
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval));
|
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval));
|
||||||
assertEquals(totalGain, trackPoints.size() * TestDataUtil.ALTITUDE_GAIN, 0.1);
|
assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1);
|
||||||
|
|
||||||
for (int i = 0; i < intervalList.size() - 1; i++) {
|
for (int i = 0; i < intervalList.size() - 1; i++) {
|
||||||
assertEquals(intervalList.get(i).getDistance().toM(), distanceInterval, 0.001);
|
assertEquals(intervalList.get(i).getDistance().toM(), distanceInterval, 0.001);
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
case 0:
|
case 0:
|
||||||
return StatisticsRecordedFragment.newInstance(trackId);
|
return StatisticsRecordedFragment.newInstance(trackId);
|
||||||
case 1:
|
case 1:
|
||||||
return IntervalsFragment.newInstance(true);
|
return IntervalsFragment.newInstance(trackId, true);
|
||||||
case 2:
|
case 2:
|
||||||
return ChartFragment.newInstance(false);
|
return ChartFragment.newInstance(false);
|
||||||
case 3:
|
case 3:
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
case 0:
|
case 0:
|
||||||
return StatisticsRecordingFragment.newInstance();
|
return StatisticsRecordingFragment.newInstance();
|
||||||
case 1:
|
case 1:
|
||||||
return IntervalsFragment.newInstance(false);
|
return IntervalsFragment.newInstance(trackId, false);
|
||||||
case 2:
|
case 2:
|
||||||
return ChartFragment.newInstance(false);
|
return ChartFragment.newInstance(false);
|
||||||
case 3:
|
case 3:
|
||||||
|
|||||||
@@ -72,10 +72,6 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<IntervalStatistics.Interval> swapData(List<IntervalStatistics.Interval> data, boolean metricUnits, boolean isReportSpeed) {
|
public List<IntervalStatistics.Interval> swapData(List<IntervalStatistics.Interval> data, boolean metricUnits, boolean isReportSpeed) {
|
||||||
if (intervalList == data && this.metricUnits == metricUnits && this.isReportSpeed == isReportSpeed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.metricUnits = metricUnits;
|
this.metricUnits = metricUnits;
|
||||||
this.isReportSpeed = isReportSpeed;
|
this.isReportSpeed = isReportSpeed;
|
||||||
intervalList = data;
|
intervalList = data;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import java.util.Collections;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.BuildConfig;
|
import de.dennisguse.opentracks.BuildConfig;
|
||||||
import de.dennisguse.opentracks.content.data.Altitude;
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
@@ -20,15 +19,10 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackActivityDataHubInterface;
|
|
||||||
import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
|
import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
|
||||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
|
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||||
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
||||||
@@ -36,31 +30,31 @@ import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
|||||||
/**
|
/**
|
||||||
* A fragment to display the intervals from recorded track.
|
* A fragment to display the intervals from recorded track.
|
||||||
*/
|
*/
|
||||||
public class IntervalsFragment extends Fragment implements TrackDataListener {
|
public class IntervalsFragment extends Fragment {
|
||||||
|
|
||||||
private static final String TAG = IntervalsFragment.class.getSimpleName();
|
private static final String TAG = IntervalsFragment.class.getSimpleName();
|
||||||
|
|
||||||
private static final String FROM_TOP_TO_BOTTOM_KEY = "fromTopToBottom";
|
private static final String FROM_TOP_TO_BOTTOM_KEY = "fromTopToBottom";
|
||||||
|
private static final String TRACK_ID_KEY = "trackId";
|
||||||
|
|
||||||
private IntervalStatisticsModel viewModel;
|
private IntervalStatisticsModel viewModel;
|
||||||
protected IntervalStatisticsAdapter.StackMode stackModeListView;
|
protected IntervalStatisticsAdapter.StackMode stackModeListView;
|
||||||
private IntervalStatisticsModel.IntervalOption selectedInterval;
|
private IntervalStatisticsModel.IntervalOption selectedInterval;
|
||||||
|
|
||||||
|
private Track.Id trackId;
|
||||||
private boolean metricUnits;
|
private boolean metricUnits;
|
||||||
private IntervalStatisticsAdapter adapter;
|
private IntervalStatisticsAdapter adapter;
|
||||||
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
|
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
private TrackDataHub trackDataHub;
|
|
||||||
private boolean isReportSpeed;
|
private boolean isReportSpeed;
|
||||||
|
|
||||||
private IntervalListViewBinding viewBinding;
|
private IntervalListViewBinding viewBinding;
|
||||||
|
|
||||||
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||||
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
updateIntervals(PreferencesUtils.isMetricUnits(sharedPreferences, getContext()), selectedInterval);
|
||||||
uploadIntervals();
|
|
||||||
if (spinnerAdapter != null) {
|
if (spinnerAdapter != null) {
|
||||||
spinnerAdapter.notifyDataSetChanged();
|
spinnerAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
@@ -70,12 +64,14 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
/**
|
/**
|
||||||
* Creates an instance of this class.
|
* Creates an instance of this class.
|
||||||
*
|
*
|
||||||
|
* @param trackId
|
||||||
* @param fromTopToBottom If true then the intervals are shown from top to bottom (the first interval on top). Otherwise the intervals are shown from bottom to top.
|
* @param fromTopToBottom If true then the intervals are shown from top to bottom (the first interval on top). Otherwise the intervals are shown from bottom to top.
|
||||||
* @return IntervalsFragment instance.
|
* @return IntervalsFragment instance.
|
||||||
*/
|
*/
|
||||||
public static Fragment newInstance(boolean fromTopToBottom) {
|
public static Fragment newInstance(Track.Id trackId, boolean fromTopToBottom) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putBoolean(FROM_TOP_TO_BOTTOM_KEY, fromTopToBottom);
|
bundle.putBoolean(FROM_TOP_TO_BOTTOM_KEY, fromTopToBottom);
|
||||||
|
bundle.putParcelable(TRACK_ID_KEY, trackId);
|
||||||
IntervalsFragment intervalsFragment = new IntervalsFragment();
|
IntervalsFragment intervalsFragment = new IntervalsFragment();
|
||||||
intervalsFragment.setArguments(bundle);
|
intervalsFragment.setArguments(bundle);
|
||||||
return intervalsFragment;
|
return intervalsFragment;
|
||||||
@@ -85,6 +81,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
stackModeListView = getArguments().getBoolean(FROM_TOP_TO_BOTTOM_KEY, true) ? IntervalStatisticsAdapter.StackMode.STACK_FROM_TOP : IntervalStatisticsAdapter.StackMode.STACK_FROM_BOTTOM;
|
stackModeListView = getArguments().getBoolean(FROM_TOP_TO_BOTTOM_KEY, true) ? IntervalStatisticsAdapter.StackMode.STACK_FROM_TOP : IntervalStatisticsAdapter.StackMode.STACK_FROM_BOTTOM;
|
||||||
|
trackId = getArguments().getParcelable(TRACK_ID_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -127,8 +124,7 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
viewBinding.spinnerIntervals.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
viewBinding.spinnerIntervals.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||||
selectedInterval = IntervalStatisticsModel.IntervalOption.values()[i];
|
updateIntervals(metricUnits, IntervalStatisticsModel.IntervalOption.values()[i]);
|
||||||
uploadIntervals();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -141,11 +137,15 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
resumeTrackDataHub();
|
|
||||||
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||||
|
|
||||||
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getContext());
|
||||||
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
|
if (track != null) {
|
||||||
|
isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
|
||||||
|
}
|
||||||
|
|
||||||
viewModel = new ViewModelProvider(getActivity()).get(IntervalStatisticsModel.class);
|
viewModel = new ViewModelProvider(getActivity()).get(IntervalStatisticsModel.class);
|
||||||
loadIntervals();
|
loadIntervals();
|
||||||
}
|
}
|
||||||
@@ -153,8 +153,8 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
pauseTrackDataHub();
|
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
viewModel.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -180,88 +180,17 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
|||||||
if (viewModel == null) {
|
if (viewModel == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LiveData<List<IntervalStatistics.Interval>> liveData = viewModel.getIntervalStats(metricUnits, selectedInterval);
|
LiveData<List<IntervalStatistics.Interval>> liveData = viewModel.getIntervalStats(trackId, metricUnits, selectedInterval);
|
||||||
liveData.observe(getActivity(), intervalList -> adapter.swapData(intervalList, metricUnits, isReportSpeed));
|
liveData.observe(getActivity(), intervalList -> adapter.swapData(intervalList, metricUnits, isReportSpeed));
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void uploadIntervals() {
|
private synchronized void updateIntervals(boolean metricUnits, IntervalStatisticsModel.IntervalOption selectedInterval) {
|
||||||
if (viewModel == null) {
|
boolean update = metricUnits != this.metricUnits || !selectedInterval.sameMultiplier(this.selectedInterval);
|
||||||
return;
|
this.metricUnits = metricUnits;
|
||||||
}
|
this.selectedInterval = selectedInterval;
|
||||||
viewModel.upload(metricUnits, selectedInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (update && viewModel != null) {
|
||||||
* Resumes the trackDataHub.
|
viewModel.update(trackId, this.metricUnits, this.selectedInterval);
|
||||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
|
||||||
*/
|
|
||||||
private synchronized void resumeTrackDataHub() {
|
|
||||||
trackDataHub = ((TrackActivityDataHubInterface) getActivity()).getTrackDataHub();
|
|
||||||
trackDataHub.registerTrackDataListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pauses the trackDataHub.
|
|
||||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
|
||||||
*/
|
|
||||||
private synchronized void pauseTrackDataHub() {
|
|
||||||
trackDataHub.unregisterTrackDataListener(this);
|
|
||||||
trackDataHub = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTrackUpdated(Track track) {
|
|
||||||
if (isResumed()) {
|
|
||||||
getActivity().runOnUiThread(() -> {
|
|
||||||
if (isResumed()) {
|
|
||||||
// Set category.
|
|
||||||
String category = track != null ? track.getCategory() : "";
|
|
||||||
|
|
||||||
// Set rate label.
|
|
||||||
isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
|
|
||||||
viewBinding.intervalRate.setText(isReportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearTrackPoints() {
|
|
||||||
if (isResumed() && viewModel != null) {
|
|
||||||
viewModel.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics unused, Speed unused2, double unused3) {
|
|
||||||
if (isResumed() && viewModel != null) {
|
|
||||||
viewModel.add(trackPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics unused) {
|
|
||||||
if (isResumed() && viewModel != null) {
|
|
||||||
viewModel.add(trackPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNewTrackPointsDone() {
|
|
||||||
if (isResumed() && viewModel != null) {
|
|
||||||
runOnUiThread(viewModel::onNewTrackPoints);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs a runnable on the UI thread.
|
|
||||||
*
|
|
||||||
* @param runnable the runnable
|
|
||||||
*/
|
|
||||||
private void runOnUiThread(Runnable runnable) {
|
|
||||||
FragmentActivity fragmentActivity = getActivity();
|
|
||||||
if (fragmentActivity != null) {
|
|
||||||
fragmentActivity.runOnUiThread(runnable);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,18 +178,13 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
|||||||
}
|
}
|
||||||
String category = track.getCategory();
|
String category = track.getCategory();
|
||||||
|
|
||||||
//TODO Querying all TrackPoints all the time is inefficient; get infos from TrackRecordingService
|
|
||||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
|
|
||||||
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
|
||||||
while (trackPointIterator.hasNext()) {
|
|
||||||
trackPoints.add(trackPointIterator.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
|
boolean isMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
|
||||||
boolean isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
|
boolean isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
|
||||||
Distance minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
Distance minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
||||||
|
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.one(isMetricUnits), minGPSDistance);
|
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
|
||||||
|
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.one(isMetricUnits), minGPSDistance);
|
||||||
|
intervalStatistics.addTrackPoints(trackPointIterator);
|
||||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
|
IntervalStatistics.Interval lastInterval = intervalStatistics.getLastInterval();
|
||||||
|
|
||||||
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, isMetricUnits, isReportSpeed, lastInterval);
|
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, isMetricUnits, isReportSpeed, lastInterval);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.dennisguse.opentracks.viewmodels;
|
package de.dennisguse.opentracks.viewmodels;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@@ -10,51 +9,72 @@ import java.util.List;
|
|||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
|
|
||||||
public class IntervalStatistics {
|
public class IntervalStatistics {
|
||||||
private final List<Interval> intervalList = new ArrayList<>();
|
private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||||
|
private final List<Interval> intervalList;
|
||||||
private final Distance distanceInterval;
|
private final Distance distanceInterval;
|
||||||
|
private final Distance minGPSDistance;
|
||||||
|
private Interval interval, lastInterval;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param trackPoints the list of TrackPoint.
|
|
||||||
* @param distanceInterval distance of every interval.
|
* @param distanceInterval distance of every interval.
|
||||||
* @param minGPSDistance the setting value for GPS distance.
|
* @param minGPSDistance the setting value for GPS distance.
|
||||||
*/
|
*/
|
||||||
public IntervalStatistics(@NonNull List<TrackPoint> trackPoints, Distance distanceInterval, Distance minGPSDistance) {
|
public IntervalStatistics(Distance distanceInterval, Distance minGPSDistance) {
|
||||||
intervalList.clear();
|
|
||||||
this.distanceInterval = distanceInterval;
|
this.distanceInterval = distanceInterval;
|
||||||
|
this.minGPSDistance = minGPSDistance;
|
||||||
|
|
||||||
if (trackPoints.size() == 0) {
|
interval = new Interval();
|
||||||
return;
|
lastInterval = new Interval();
|
||||||
}
|
intervalList = new ArrayList<>();
|
||||||
|
intervalList.add(lastInterval);
|
||||||
|
}
|
||||||
|
|
||||||
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
/**
|
||||||
Interval interval = new Interval();
|
* Complete intervals with the tracks points from the iterator.
|
||||||
|
*
|
||||||
|
* @param trackPointIterator
|
||||||
|
* @return the last track point's id used to compute the intervals.
|
||||||
|
*/
|
||||||
|
public TrackPoint.Id addTrackPoints(TrackPointIterator trackPointIterator) {
|
||||||
|
boolean newIntervalAdded = false;
|
||||||
|
TrackPoint trackPoint = null;
|
||||||
|
|
||||||
for (TrackPoint trackPoint : trackPoints) {
|
while (trackPointIterator.hasNext()) {
|
||||||
|
trackPoint = trackPointIterator.next();
|
||||||
trackStatisticsUpdater.addTrackPoint(trackPoint, minGPSDistance);
|
trackStatisticsUpdater.addTrackPoint(trackPoint, minGPSDistance);
|
||||||
|
|
||||||
if (trackStatisticsUpdater.getTrackStatistics().getTotalDistance().plus(interval.distance).greaterOrEqualThan(distanceInterval)) {
|
if (trackStatisticsUpdater.getTrackStatistics().getTotalDistance().plus(interval.distance).greaterOrEqualThan(distanceInterval)) {
|
||||||
interval.update(trackStatisticsUpdater.getTrackStatistics(), trackPoint);
|
interval.add(trackStatisticsUpdater.getTrackStatistics(), trackPoint);
|
||||||
|
|
||||||
double adjustFactor = distanceInterval.dividedBy(interval.distance);
|
double adjustFactor = distanceInterval.dividedBy(interval.distance);
|
||||||
Interval adjustedInterval = new Interval(interval);
|
Interval adjustedInterval = new Interval(interval);
|
||||||
adjustedInterval.adjust(adjustFactor);
|
adjustedInterval.adjust(adjustFactor);
|
||||||
|
|
||||||
intervalList.add(adjustedInterval);
|
intervalList.set(intervalList.size() - 1, adjustedInterval);
|
||||||
|
|
||||||
interval = new Interval(interval.distance.minus(adjustedInterval.distance), interval.time.minus(adjustedInterval.time));
|
interval = new Interval(interval.distance.minus(adjustedInterval.distance), interval.time.minus(adjustedInterval.time));
|
||||||
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||||
trackStatisticsUpdater.addTrackPoint(trackPoint, minGPSDistance);
|
trackStatisticsUpdater.addTrackPoint(trackPoint, minGPSDistance);
|
||||||
|
|
||||||
|
lastInterval = new Interval(interval);
|
||||||
|
intervalList.add(lastInterval);
|
||||||
|
|
||||||
|
newIntervalAdded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackStatisticsUpdater.getTrackStatistics().getTotalDistance().toM() >= 1d) {
|
if (newIntervalAdded) {
|
||||||
interval.update(trackStatisticsUpdater.getTrackStatistics(), null);
|
lastInterval.add(trackStatisticsUpdater.getTrackStatistics(), null);
|
||||||
intervalList.add(interval);
|
} else {
|
||||||
|
lastInterval.set(trackStatisticsUpdater.getTrackStatistics());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return trackPoint != null ? trackPoint.getId() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Interval> getIntervalList() {
|
public List<Interval> getIntervalList() {
|
||||||
@@ -110,7 +130,7 @@ public class IntervalStatistics {
|
|||||||
loss_m = i.loss_m;
|
loss_m = i.loss_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void adjust(double adjustFactor) {
|
private void adjust(double adjustFactor) {
|
||||||
distance = distance.multipliedBy(adjustFactor);
|
distance = distance.multipliedBy(adjustFactor);
|
||||||
time = Duration.ofMillis((long) (time.toMillis() * adjustFactor));
|
time = Duration.ofMillis((long) (time.toMillis() * adjustFactor));
|
||||||
}
|
}
|
||||||
@@ -131,7 +151,7 @@ public class IntervalStatistics {
|
|||||||
return loss_m;
|
return loss_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) {
|
private void add(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) {
|
||||||
distance = distance.plus(trackStatistics.getTotalDistance());
|
distance = distance.plus(trackStatistics.getTotalDistance());
|
||||||
time = time.plus(trackStatistics.getTotalTime());
|
time = time.plus(trackStatistics.getTotalTime());
|
||||||
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
|
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
|
||||||
@@ -141,5 +161,12 @@ public class IntervalStatistics {
|
|||||||
loss_m = lastTrackPoint.hasAltitudeLoss() ? loss_m - lastTrackPoint.getAltitudeLoss() : loss_m;
|
loss_m = lastTrackPoint.hasAltitudeLoss() ? loss_m - lastTrackPoint.getAltitudeLoss() : loss_m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void set(TrackStatistics trackStatistics) {
|
||||||
|
distance = trackStatistics.getTotalDistance();
|
||||||
|
time = trackStatistics.getTotalTime();
|
||||||
|
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
|
||||||
|
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
package de.dennisguse.opentracks.viewmodels;
|
package de.dennisguse.opentracks.viewmodels;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This model is used to load intervals for a track.
|
* This model is used to load intervals for a track.
|
||||||
@@ -21,65 +30,92 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
|||||||
*/
|
*/
|
||||||
public class IntervalStatisticsModel extends AndroidViewModel {
|
public class IntervalStatisticsModel extends AndroidViewModel {
|
||||||
|
|
||||||
private final List<TrackPoint> trackPoints = new ArrayList<>();
|
private static final String TAG = IntervalStatisticsModel.class.getSimpleName();
|
||||||
|
|
||||||
private MutableLiveData<List<IntervalStatistics.Interval>> intervalsLiveData;
|
private MutableLiveData<List<IntervalStatistics.Interval>> intervalsLiveData;
|
||||||
|
private IntervalStatistics intervalStatistics;
|
||||||
private Distance distanceInterval;
|
private Distance distanceInterval;
|
||||||
private Distance minGPSDistance;
|
private final Distance minGPSDistance;
|
||||||
|
private final ContentResolver contentResolver;
|
||||||
|
private ContentObserver trackPointsTableObserver;
|
||||||
|
private TrackPoint.Id lastTrackPointId;
|
||||||
|
|
||||||
|
private final Executor executor = Executors.newSingleThreadExecutor();
|
||||||
|
private HandlerThread handlerThread;
|
||||||
|
private Handler handler;
|
||||||
|
|
||||||
public IntervalStatisticsModel(@NonNull Application application) {
|
public IntervalStatisticsModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(PreferencesUtils.getSharedPreferences(application), application);
|
minGPSDistance = PreferencesUtils.getRecordingDistanceInterval(PreferencesUtils.getSharedPreferences(application), application);
|
||||||
|
contentResolver = getApplication().getContentResolver();
|
||||||
|
handlerThread = new HandlerThread(TAG);
|
||||||
|
handlerThread.start();
|
||||||
|
handler = new Handler(handlerThread.getLooper());
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<IntervalStatistics.Interval>> getIntervalStats(boolean metricUnits, @Nullable IntervalOption interval) {
|
@Override
|
||||||
synchronized (trackPoints) {
|
protected void onCleared() {
|
||||||
if (intervalsLiveData == null) {
|
super.onCleared();
|
||||||
if (interval == null) {
|
if (trackPointsTableObserver != null) {
|
||||||
interval = IntervalOption.OPTION_1;
|
contentResolver.unregisterContentObserver(trackPointsTableObserver);
|
||||||
}
|
trackPointsTableObserver = null;
|
||||||
|
|
||||||
intervalsLiveData = new MutableLiveData<>();
|
|
||||||
distanceInterval = interval.getDistance(metricUnits);
|
|
||||||
loadIntervalStatistics();
|
|
||||||
}
|
|
||||||
return intervalsLiveData;
|
|
||||||
}
|
}
|
||||||
}
|
if (handlerThread != null) {
|
||||||
|
handlerThread.getLooper().quit();
|
||||||
private void loadIntervalStatistics() {
|
handlerThread = null;
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, distanceInterval, minGPSDistance);
|
|
||||||
intervalsLiveData.postValue(intervalStatistics.getIntervalList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(TrackPoint trackPoint) {
|
|
||||||
synchronized (trackPoints) {
|
|
||||||
trackPoints.add(trackPoint);
|
|
||||||
}
|
}
|
||||||
|
handler = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onNewTrackPoints() {
|
public MutableLiveData<List<IntervalStatistics.Interval>> getIntervalStats(Track.Id trackId, boolean metricUnits, @Nullable IntervalOption interval) {
|
||||||
synchronized (trackPoints) {
|
if (intervalsLiveData == null) {
|
||||||
if (intervalsLiveData != null) {
|
|
||||||
loadIntervalStatistics();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
synchronized (trackPoints) {
|
|
||||||
trackPoints.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void upload(boolean metricUnits, @Nullable IntervalOption interval) {
|
|
||||||
synchronized (trackPoints) {
|
|
||||||
if (interval == null) {
|
if (interval == null) {
|
||||||
interval = IntervalOption.OPTION_1;
|
interval = IntervalOption.OPTION_1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
intervalsLiveData = new MutableLiveData<>();
|
||||||
distanceInterval = interval.getDistance(metricUnits);
|
distanceInterval = interval.getDistance(metricUnits);
|
||||||
loadIntervalStatistics();
|
intervalStatistics = new IntervalStatistics(distanceInterval, minGPSDistance);
|
||||||
|
|
||||||
|
loadIntervalStatistics(trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trackPointsTableObserver = new ContentObserver(handler) {
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange, Uri uri) {
|
||||||
|
loadIntervalStatistics(trackId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_TRACKID, false, trackPointsTableObserver);
|
||||||
|
|
||||||
|
return intervalsLiveData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadIntervalStatistics(Track.Id trackId) {
|
||||||
|
executor.execute(() -> {
|
||||||
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication());
|
||||||
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, lastTrackPointId)) {
|
||||||
|
lastTrackPointId = intervalStatistics.addTrackPoints(trackPointIterator);
|
||||||
|
intervalsLiveData.postValue(intervalStatistics.getIntervalList());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPause() {
|
||||||
|
if (trackPointsTableObserver != null) {
|
||||||
|
contentResolver.unregisterContentObserver(trackPointsTableObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(Track.Id trackId, boolean metricUnits, @Nullable IntervalOption interval) {
|
||||||
|
if (interval == null) {
|
||||||
|
interval = IntervalOption.OPTION_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTrackPointId = null;
|
||||||
|
distanceInterval = interval.getDistance(metricUnits);
|
||||||
|
intervalStatistics = new IntervalStatistics(distanceInterval, minGPSDistance);
|
||||||
|
loadIntervalStatistics(trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,6 +143,10 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
|||||||
.multipliedBy(multiplier);
|
.multipliedBy(multiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean sameMultiplier(IntervalOption intervalOption) {
|
||||||
|
return intervalOption != null && this.multiplier == intervalOption.multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "" + multiplier; //TODO Somehow IntervalsFragment relies on a parsable Integer.
|
return "" + multiplier; //TODO Somehow IntervalsFragment relies on a parsable Integer.
|
||||||
|
|||||||
Reference in New Issue
Block a user