Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d240b457e2 | |||
| 6d800caab1 | |||
| 7bf153db39 | |||
| 1b69f29751 | |||
| dfc3837739 | |||
| 30e847866e | |||
| fbc902e830 | |||
| 208f1d71ba | |||
| 38a15a96b3 | |||
| 46f8753746 | |||
| 70bc8f33b1 | |||
| 6cd27ab096 | |||
| 5581b404f7 | |||
| 2ef74eb908 | |||
| 16c654141f | |||
| dfc785f2fd | |||
| f621cdf032 | |||
| 8b04d9c4a6 | |||
| 364b4702e3 | |||
| a98c16de28 | |||
| 881bf1982a | |||
| f340622977 | |||
| 194379295d | |||
| 901cffee97 | |||
| d93c2ec5f3 | |||
| 306522863a | |||
| e54d85e04d | |||
| cb30ad27c3 | |||
| 8685c9cc88 | |||
| a6f03d0f23 | |||
| e3c812b155 | |||
| f149de9ce3 | |||
| 3447dd2569 | |||
| b2b8c7fd54 | |||
| 486bcd0aec | |||
| c8a33201f5 | |||
| 4a37c2cc7e | |||
| d23db5bafd | |||
| ac528f9a10 | |||
| 256eadd49d | |||
| 64e58934a5 | |||
| 92dfadfdba | |||
| cbce70143b | |||
| f4b9e3159e |
@@ -45,19 +45,16 @@ captures/
|
||||
|
||||
|
||||
# IntelliJ IDEA
|
||||
*.iml
|
||||
*.iws
|
||||
/out/
|
||||
|
||||
# User-specific configurations
|
||||
.idea/.name
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/caches/
|
||||
.idea/compiler.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
.idea/dataSources.ids
|
||||
.idea/datasources.xml
|
||||
.idea/dictionaries
|
||||
.idea/dynamic.xml
|
||||
.idea/encodings.xml
|
||||
.idea/jsLibraryMappings.xml
|
||||
@@ -67,10 +64,8 @@ captures/
|
||||
.idea/scopes/scope_settings.xml
|
||||
.idea/shelf/
|
||||
.idea/sqlDataSources.xml
|
||||
.idea/tasks.xml
|
||||
.idea/vcs.xml
|
||||
.idea/uiDesigner.xml
|
||||
.idea/workspace.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
#See https://docs.travis-ci.com/user/languages/android/
|
||||
#See https://github.com/ankidroid/Anki-Android/blob/master/.travis.yml
|
||||
language: android
|
||||
|
||||
env:
|
||||
global:
|
||||
- ADB_INSTALL_TIMEOUT=8
|
||||
- ABI=armeabi-v7a
|
||||
- EMU_FLAVOR=default
|
||||
# PATH order is incredibly important. e.g. the 'emulator' script exists in more than one place!
|
||||
- ANDROID_HOME=/usr/local/android-sdk
|
||||
- TOOLS=${ANDROID_HOME}/tools
|
||||
- PATH=${ANDROID_HOME}:${ANDROID_HOME}/emulator:${TOOLS}:${TOOLS}/bin:${ANDROID_HOME}/platform-tools:${PATH}
|
||||
matrix:
|
||||
- API=27 ABI=x86_64
|
||||
|
||||
android:
|
||||
components:
|
||||
# installing tools to start, then use `sdkmanager` below to get the rest
|
||||
- tools
|
||||
- build-tools-28.0.3
|
||||
- android-29
|
||||
|
||||
# Emulator Management: Create, Start and Wait
|
||||
install:
|
||||
- echo y | sdkmanager "platform-tools"
|
||||
- echo y | sdkmanager --channel=4 "emulator"
|
||||
- echo y | sdkmanager "system-images;android-$API;$EMU_FLAVOR;$ABI"
|
||||
- echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M
|
||||
- emulator -verbose -avd test -no-accel -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048 &
|
||||
- android-wait-for-emulator
|
||||
- adb shell input keyevent 82 &
|
||||
|
||||
script:
|
||||
- ./gradlew build
|
||||
- ./gradlew connectedCheck
|
||||
|
||||
sbefore_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
- $HOME/.android/build-cache
|
||||
@@ -0,0 +1,14 @@
|
||||
v3.5.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Marker: select pictures from gallery
|
||||
- Marker: share picture
|
||||
- Use track name as filename when sharing
|
||||
- Removed elevation min/max (was imprecise)
|
||||
|
||||
Bugfix:
|
||||
- Charts use preferred units and rate
|
||||
|
||||
Developer:
|
||||
- Database: remove unused columns
|
||||
- Database: add indices
|
||||
@@ -0,0 +1,10 @@
|
||||
v3.6.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Stopped tracks can now be resumed
|
||||
|
||||
Bugfix:
|
||||
- Import of KMZ did not process the photo URL correctly
|
||||
|
||||
Developer:
|
||||
- Simplified data processing
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 168 KiB |
@@ -49,7 +49,7 @@ public class TrackStubUtils {
|
||||
public static Track createTrack(int numberOfLocations) {
|
||||
Track track = new Track();
|
||||
for (int i = 0; i < numberOfLocations; i++) {
|
||||
track.addLocation(createSensorDataSetLocation(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE + i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
|
||||
track.addTrackPoint(createDefaultTrackPoint(INITIAL_LATITUDE + i * DIFFERENCE, INITIAL_LONGITUDE + i * DIFFERENCE, INITIAL_ALTITUDE + i * DIFFERENCE));
|
||||
}
|
||||
|
||||
return track;
|
||||
@@ -60,8 +60,8 @@ public class TrackStubUtils {
|
||||
*
|
||||
* @return a track stub.
|
||||
*/
|
||||
public static TrackPoint createSensorDataSetLocation() {
|
||||
return createSensorDataSetLocation(INITIAL_LATITUDE, INITIAL_LONGITUDE, INITIAL_ALTITUDE);
|
||||
public static TrackPoint createDefaultTrackPoint() {
|
||||
return createDefaultTrackPoint(INITIAL_LATITUDE, INITIAL_LONGITUDE, INITIAL_ALTITUDE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,18 +69,16 @@ public class TrackStubUtils {
|
||||
*
|
||||
* @return a SensorDataSetLocation stub.
|
||||
*/
|
||||
private static TrackPoint createSensorDataSetLocation(double latitude, double longitude, double altitude) {
|
||||
// Initial Location
|
||||
Location loc = new Location(LOCATION_PROVIDER);
|
||||
loc.setLatitude(latitude);
|
||||
loc.setLongitude(longitude);
|
||||
loc.setAltitude(altitude);
|
||||
loc.setAccuracy(INITIAL_ACCURACY);
|
||||
loc.setSpeed(INITIAL_SPEED);
|
||||
loc.setTime(INITIAL_TIME);
|
||||
loc.setBearing(INITIAL_BEARING);
|
||||
SensorDataSet sd = new SensorDataSet(Float.NaN, Float.NaN);
|
||||
private static TrackPoint createDefaultTrackPoint(double latitude, double longitude, double altitude) {
|
||||
Location location = new Location(LOCATION_PROVIDER);
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
location.setAltitude(altitude);
|
||||
location.setAccuracy(INITIAL_ACCURACY);
|
||||
location.setSpeed(INITIAL_SPEED);
|
||||
location.setTime(INITIAL_TIME);
|
||||
location.setBearing(INITIAL_BEARING);
|
||||
|
||||
return new TrackPoint(loc, sd);
|
||||
return new TrackPoint(location, new SensorDataSet(Float.NaN, Float.NaN));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.chart;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
@@ -44,7 +45,17 @@ public class ChartValueSeriesTest {
|
||||
R.string.description_elevation_metric,
|
||||
R.string.description_elevation_imperial,
|
||||
R.color.chart_elevation_fill,
|
||||
R.color.chart_elevation_border);
|
||||
R.color.chart_elevation_border) {
|
||||
@Override
|
||||
double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getElevation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawIfChartPointHasNoData() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,8 +74,8 @@ public class ChartValueSeriesTest {
|
||||
|
||||
@Test
|
||||
public void testSmallUpdates() {
|
||||
series.update(0);
|
||||
series.update(10);
|
||||
series.update(new ChartPoint(0));
|
||||
series.update(new ChartPoint(10));
|
||||
series.updateDimension();
|
||||
Assert.assertEquals(100, series.getInterval());
|
||||
Assert.assertEquals(0, series.getMinMarkerValue());
|
||||
@@ -73,8 +84,8 @@ public class ChartValueSeriesTest {
|
||||
|
||||
@Test
|
||||
public void testBigUpdates() {
|
||||
series.update(0);
|
||||
series.update(901);
|
||||
series.update(new ChartPoint(0));
|
||||
series.update(new ChartPoint(901));
|
||||
series.updateDimension();
|
||||
Assert.assertEquals(1000, series.getInterval());
|
||||
Assert.assertEquals(0, series.getMinMarkerValue());
|
||||
@@ -83,8 +94,8 @@ public class ChartValueSeriesTest {
|
||||
|
||||
@Test
|
||||
public void testNotZeroBasedUpdates() {
|
||||
series.update(220);
|
||||
series.update(250);
|
||||
series.update(new ChartPoint(220));
|
||||
series.update(new ChartPoint(250));
|
||||
series.updateDimension();
|
||||
Assert.assertEquals(100, series.getInterval());
|
||||
Assert.assertEquals(200, series.getMinMarkerValue());
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -42,7 +42,7 @@ public class DescriptionGeneratorTest {
|
||||
private static final long START_TIME = 1288721514000L;
|
||||
private DescriptionGenerator descriptionGenerator;
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -55,7 +55,7 @@ public class DescriptionGeneratorTest {
|
||||
@Test
|
||||
public void testGenerateTrackDescription() {
|
||||
Track track = new Track();
|
||||
TripStatistics stats = new TripStatistics();
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
@@ -63,10 +63,8 @@ public class DescriptionGeneratorTest {
|
||||
stats.setMaxElevation(550);
|
||||
stats.setMinElevation(-500);
|
||||
stats.setTotalElevationGain(6000);
|
||||
stats.setMaxGrade(0.42);
|
||||
stats.setMinGrade(0.11);
|
||||
stats.setStartTime(START_TIME);
|
||||
track.setTripStatistics(stats);
|
||||
stats.setStartTime_ms(START_TIME);
|
||||
track.setTrackStatistics(stats);
|
||||
track.setCategory("hiking");
|
||||
String expected = //"Created by"
|
||||
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>"
|
||||
@@ -85,8 +83,6 @@ public class DescriptionGeneratorTest {
|
||||
+ "Max elevation: 550 m (1804 ft)<br>"
|
||||
+ "Min elevation: -500 m (-1640 ft)<br>"
|
||||
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
||||
+ "Max grade: 42 %<br>"
|
||||
+ "Min grade: 11 %<br>"
|
||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||
|
||||
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||
@@ -142,34 +138,4 @@ public class DescriptionGeneratorTest {
|
||||
descriptionGenerator.writePace(1.1, builder, R.string.description_average_pace_in_minute, "\n");
|
||||
Assert.assertEquals("Average pace: 15:09 min/km (24:23 min/mi)\n", builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteGrade() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
descriptionGenerator.writeGrade(.042, builder, R.string.description_max_grade, "<br>");
|
||||
Assert.assertEquals("Max grade: 4 %<br>", builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with a NaN.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteGrade_nan() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
descriptionGenerator.writeGrade(Double.NaN, builder, R.string.description_max_grade, "<br>");
|
||||
Assert.assertEquals("Max grade: 0 %<br>", builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGenerator#writeGrade(double, StringBuilder, int, String)} with an infinite number.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteGrade_infinite() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
descriptionGenerator.writeGrade(Double.POSITIVE_INFINITY, builder, R.string.description_max_grade, "<br>");
|
||||
Assert.assertEquals("Max grade: 0 %<br>", builder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* Tests for {@link SearchEngine}.
|
||||
@@ -56,7 +56,7 @@ public class SearchEngineTest {
|
||||
private ContentProviderUtils providerUtils;
|
||||
private SearchEngine engine;
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
|
||||
@Before
|
||||
@@ -66,7 +66,7 @@ public class SearchEngineTest {
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
public void tearDown() {
|
||||
providerUtils.deleteAllTracks(context);
|
||||
}
|
||||
|
||||
@@ -76,11 +76,11 @@ public class SearchEngineTest {
|
||||
track.setDescription(description);
|
||||
track.setCategory(category);
|
||||
|
||||
TripStatistics stats = track.getTripStatistics();
|
||||
TrackStatistics stats = track.getTrackStatistics();
|
||||
if (hoursAgo > 0) {
|
||||
// Started twice hoursAgo, so the average time is hoursAgo.
|
||||
stats.setStartTime(NOW - hoursAgo * 1000L * 60L * 60L * 2);
|
||||
stats.setStopTime(NOW);
|
||||
stats.setStartTime_ms(NOW - hoursAgo * 1000L * 60L * 60L * 2);
|
||||
stats.setStopTime_ms(NOW);
|
||||
}
|
||||
|
||||
Uri uri = providerUtils.insertTrack(track);
|
||||
@@ -100,19 +100,17 @@ public class SearchEngineTest {
|
||||
}
|
||||
|
||||
private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) {
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setName(title);
|
||||
waypoint.setDescription(description);
|
||||
waypoint.setCategory(category);
|
||||
waypoint.setTrackId(trackId);
|
||||
|
||||
Location location = new Location(HERE);
|
||||
location.setLatitude(location.getLatitude() + distance);
|
||||
location.setLongitude(location.getLongitude() + distance);
|
||||
if (hoursAgo >= 0) {
|
||||
location.setTime(NOW - hoursAgo * 1000L * 60L * 60L);
|
||||
}
|
||||
waypoint.setLocation(location);
|
||||
Waypoint waypoint = new Waypoint(location);
|
||||
waypoint.setName(title);
|
||||
waypoint.setDescription(description);
|
||||
waypoint.setCategory(category);
|
||||
waypoint.setTrackId(trackId);
|
||||
|
||||
Uri uri = providerUtils.insertWaypoint(waypoint);
|
||||
return ContentUris.parseId(uri);
|
||||
@@ -153,7 +151,7 @@ public class SearchEngineTest {
|
||||
long allMatchId = insertTrack("aa", "ba", "ca");
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
|
||||
|
||||
// Title > Description > Category.
|
||||
assertTrackResults(results, allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, categoryMatchId);
|
||||
@@ -178,7 +176,7 @@ public class SearchEngineTest {
|
||||
long allMatchId = insertWaypoint("aa", "ba", "ca");
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
|
||||
|
||||
// Title > Description > Category.
|
||||
assertWaypointResults(results, allMatchId, titleDescriptionMatchId, titleCategoryMatchId, titleMatchId, descriptionMatchId, categoryMatchId);
|
||||
@@ -199,7 +197,7 @@ public class SearchEngineTest {
|
||||
long trackId = insertTrack("aa", "bb", "cc");
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
|
||||
|
||||
// Title > Description > Category.
|
||||
Assert.assertEquals(results.toString(), 3, results.size());
|
||||
@@ -216,7 +214,7 @@ public class SearchEngineTest {
|
||||
long farId = insertWaypoint("ac", 0.2);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", HERE, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
|
||||
|
||||
// Distance order.
|
||||
assertWaypointResults(results, nearId, farId, farFarAwayId);
|
||||
@@ -229,7 +227,7 @@ public class SearchEngineTest {
|
||||
long oldId = insertTrack("ac", 2);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
|
||||
|
||||
// Reverse time order.
|
||||
assertTrackResults(results, recentId, oldId, oldestId);
|
||||
@@ -243,7 +241,7 @@ public class SearchEngineTest {
|
||||
long oldId = insertWaypoint("ac", 1);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, -1, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
|
||||
|
||||
// Reverse time order.
|
||||
assertWaypointResults(results, recentId, oldId, oldestId);
|
||||
@@ -256,7 +254,7 @@ public class SearchEngineTest {
|
||||
long otherId = insertTrack("aa", 1);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, currentId, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
|
||||
|
||||
// Current track should be demoted.
|
||||
assertTrackResults(results, otherId, currentId);
|
||||
@@ -269,7 +267,7 @@ public class SearchEngineTest {
|
||||
long currentId = insertWaypoint("ab", 1, 123);
|
||||
|
||||
SearchQuery query = new SearchQuery("a", null, 123, NOW);
|
||||
ArrayList<ScoredResult> results = new ArrayList<ScoredResult>(engine.search(query));
|
||||
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
|
||||
|
||||
// Waypoint in current track should be promoted.
|
||||
assertWaypointResults(results, currentId, otherId);
|
||||
|
||||
@@ -19,9 +19,8 @@ public class TestDataUtil {
|
||||
Track track = new Track();
|
||||
track.setId(id);
|
||||
track.setName("Test: " + id);
|
||||
track.setNumberOfPoints(numPoints);
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
track.addLocation(createLocation(i));
|
||||
track.addTrackPoint(createTrackPoint(i));
|
||||
}
|
||||
return track;
|
||||
}
|
||||
@@ -32,13 +31,13 @@ public class TestDataUtil {
|
||||
* @param i the index to set the value of location.
|
||||
* @return created location
|
||||
*/
|
||||
public static Location createLocation(int i) {
|
||||
Location loc = new Location("test");
|
||||
loc.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
loc.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
loc.setAccuracy((float) i / 100.0f);
|
||||
loc.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
loc.setTime(i + 1);
|
||||
return loc;
|
||||
public static TrackPoint createTrackPoint(int i) {
|
||||
Location location = new Location("test");
|
||||
location.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
location.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
location.setAccuracy((float) i / 100.0f);
|
||||
location.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
location.setTime(i + 1);
|
||||
return new TrackPoint(location);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -38,30 +37,15 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
*/
|
||||
public class CustomContentProviderTest {
|
||||
|
||||
private static final String DATABASE_NAME = "test.db";
|
||||
|
||||
private SQLiteDatabase db;
|
||||
private CustomContentProvider customContentProvider;
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context.deleteDatabase(DATABASE_NAME);
|
||||
db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase();
|
||||
customContentProvider = new CustomContentProvider() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link CustomSQLiteOpenHelper#onCreate(SQLiteDatabase)}.
|
||||
*/
|
||||
@Test
|
||||
public void testDatabaseHelper_OnCreate() {
|
||||
Assert.assertTrue(hasTable(TracksColumns.TABLE_NAME));
|
||||
Assert.assertTrue(hasTable(TrackPointsColumns.TABLE_NAME));
|
||||
Assert.assertTrue(hasTable(WaypointsColumns.TABLE_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link CustomContentProvider#onCreate(android.content.Context)}.
|
||||
*/
|
||||
@@ -84,70 +68,4 @@ public class CustomContentProviderTest {
|
||||
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
|
||||
Assert.assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table, containing one column.
|
||||
*
|
||||
* @param table the table name
|
||||
*/
|
||||
private void createTable(String table) {
|
||||
db.execSQL("CREATE TABLE " + table + " (test INTEGER)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a table in database.
|
||||
*
|
||||
* @param table the table name
|
||||
*/
|
||||
private void dropTable(String table) {
|
||||
db.execSQL("Drop TABLE " + table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the table exists.
|
||||
*
|
||||
* @param table the table name
|
||||
*/
|
||||
private boolean hasTable(String table) {
|
||||
try {
|
||||
db.rawQuery("select count(*) from " + table, null);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the column in the table exists.
|
||||
*
|
||||
* @param table the table name
|
||||
* @param column the column name
|
||||
*/
|
||||
private boolean hasColumn(String table, String column) {
|
||||
try {
|
||||
db.execSQL("SElECT count(*) from " + table + " order by " + column);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("no such column")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up upgrade.
|
||||
*
|
||||
* @param oldVersion thd old database version
|
||||
*/
|
||||
private void setupUpgrade(int oldVersion) {
|
||||
dropTable(TracksColumns.TABLE_NAME);
|
||||
dropTable(TrackPointsColumns.TABLE_NAME);
|
||||
dropTable(WaypointsColumns.TABLE_NAME);
|
||||
createTable(TracksColumns.TABLE_NAME);
|
||||
createTable(TrackPointsColumns.TABLE_NAME);
|
||||
createTable(WaypointsColumns.TABLE_NAME);
|
||||
|
||||
CustomSQLiteOpenHelper databaseHelper = new CustomSQLiteOpenHelper(context);
|
||||
databaseHelper.onUpgrade(db, oldVersion, CustomSQLiteOpenHelper.DATABASE_VERSION);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -42,7 +41,7 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -59,7 +58,7 @@ public class CustomContentProviderUtilsTest {
|
||||
private static final String TEST_DESC = "Test Desc!";
|
||||
private static final String TEST_DESC_NEW = "Test Desc new!";
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@Mock
|
||||
@@ -76,70 +75,45 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noPoints() {
|
||||
testIterator(1, 0, 1, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_customFactory() {
|
||||
final TrackPoint location = new TrackPoint("test_location");
|
||||
final AtomicInteger counter = new AtomicInteger();
|
||||
testIterator(1, 15, 4, false, new TrackPointFactory() {
|
||||
@Override
|
||||
public TrackPoint createLocation() {
|
||||
counter.incrementAndGet();
|
||||
return location;
|
||||
}
|
||||
});
|
||||
// Make sure we were called exactly as many times as we had track points.
|
||||
Assert.assertEquals(15, counter.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_nullFactory() {
|
||||
try {
|
||||
testIterator(1, 15, 4, false, null);
|
||||
Assert.fail("Expecting IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Expected.
|
||||
}
|
||||
testIterator(1, 0, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchAscending() {
|
||||
testIterator(1, 50, 100, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 50, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 50, 100, false);
|
||||
testIterator(2, 50, 50, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noBatchDescending() {
|
||||
testIterator(1, 50, 100, true, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 50, true, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 50, 100, true);
|
||||
testIterator(2, 50, 50, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchAscending() {
|
||||
testIterator(1, 50, 11, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 25, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 50, 11, false);
|
||||
testIterator(2, 50, 25, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_batchDescending() {
|
||||
testIterator(1, 50, 11, true, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(2, 50, 25, true, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 50, 11, true);
|
||||
testIterator(2, 50, 25, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_largeTrack() {
|
||||
testIterator(1, 20000, 2000, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
testIterator(1, 20000, 2000, false);
|
||||
}
|
||||
|
||||
private List<Location> testIterator(long trackId, int numPoints, int batchSize, boolean descending, TrackPointFactory trackPointFactory) {
|
||||
private void testIterator(long trackId, int numPoints, int batchSize, boolean descending) {
|
||||
long lastPointId = initializeTrack(trackId, numPoints);
|
||||
((ContentProviderUtils) contentProviderUtils).setDefaultCursorBatchSize(batchSize);
|
||||
List<Location> locations = new ArrayList<Location>(numPoints);
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending, trackPointFactory)) {
|
||||
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
|
||||
List<TrackPoint> locations = new ArrayList<>(numPoints);
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending)) {
|
||||
while (it.hasNext()) {
|
||||
Location loc = it.next();
|
||||
TrackPoint loc = it.next();
|
||||
Assert.assertNotNull(loc);
|
||||
locations.add(loc);
|
||||
// Make sure the IDs are returned in the right order.
|
||||
@@ -148,33 +122,31 @@ public class CustomContentProviderUtilsTest {
|
||||
}
|
||||
Assert.assertEquals(numPoints, locations.size());
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
||||
private long initializeTrack(long id, int numPoints) {
|
||||
Track track = new Track();
|
||||
track.setId(id);
|
||||
track.setName("Test: " + id);
|
||||
track.setNumberOfPoints(numPoints);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
track = contentProviderUtils.getTrack(id);
|
||||
Assert.assertNotNull(track);
|
||||
|
||||
Location[] locations = new Location[numPoints];
|
||||
TrackPoint[] trackPoints = new TrackPoint[numPoints];
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
Location loc = new Location("test");
|
||||
loc.setLatitude(37.0 + (double) i / 10000.0);
|
||||
loc.setLongitude(57.0 - (double) i / 10000.0);
|
||||
loc.setAccuracy((float) i / 100.0f);
|
||||
loc.setAltitude(i * 2.5);
|
||||
locations[i] = loc;
|
||||
trackPoints[i] = new TrackPoint(loc);
|
||||
}
|
||||
contentProviderUtils.bulkInsertTrackPoint(locations, numPoints, id);
|
||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, numPoints, id);
|
||||
|
||||
// Load all inserted locations.
|
||||
// Load all inserted trackPoints.
|
||||
long lastPointId = -1;
|
||||
int counter = 0;
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L, false)) {
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
lastPointId = it.getTrackPointId();
|
||||
@@ -183,7 +155,6 @@ public class CustomContentProviderUtilsTest {
|
||||
}
|
||||
|
||||
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
|
||||
Assert.assertEquals(numPoints, track.getNumberOfPoints());
|
||||
Assert.assertEquals(numPoints, counter);
|
||||
|
||||
return lastPointId;
|
||||
@@ -340,10 +311,9 @@ public class CustomContentProviderUtilsTest {
|
||||
// Bottom
|
||||
long startTime = 1000L;
|
||||
// AverageSpeed
|
||||
double minGrade = -20.11;
|
||||
TripStatistics statistics = new TripStatistics();
|
||||
statistics.setStartTime(startTime);
|
||||
statistics.setStopTime(2500L);
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setStartTime_ms(startTime);
|
||||
statistics.setStopTime_ms(2500L);
|
||||
statistics.setTotalTime(1500L);
|
||||
statistics.setMovingTime(700L);
|
||||
statistics.setTotalDistance(750.0);
|
||||
@@ -351,22 +321,19 @@ public class CustomContentProviderUtilsTest {
|
||||
statistics.setMaxSpeed(60.0);
|
||||
statistics.setMaxElevation(1250.0);
|
||||
statistics.setMinElevation(1200.0);
|
||||
statistics.setMaxGrade(15.0);
|
||||
statistics.setMinGrade(minGrade);
|
||||
|
||||
track.setTripStatistics(statistics);
|
||||
track.setTrackStatistics(statistics);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
// Insert at first.
|
||||
Waypoint waypoint = new Waypoint();
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
|
||||
Location location = new Location("test");
|
||||
location.setLatitude(22);
|
||||
location.setLongitude(22);
|
||||
location.setAccuracy((float) 1 / 100.0f);
|
||||
location.setAltitude(2.5);
|
||||
waypoint.setLocation(location);
|
||||
|
||||
Waypoint waypoint = new Waypoint(location);
|
||||
waypoint.setDescription(TEST_DESC);
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
|
||||
@@ -446,9 +413,9 @@ public class CustomContentProviderUtilsTest {
|
||||
long trackId = System.currentTimeMillis();
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
|
||||
TripStatistics statistics = new TripStatistics();
|
||||
statistics.setStartTime(1000L);
|
||||
statistics.setStopTime(2500L);
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setStartTime_ms(1000L);
|
||||
statistics.setStopTime_ms(2500L);
|
||||
statistics.setTotalTime(1500L);
|
||||
statistics.setMovingTime(700L);
|
||||
statistics.setTotalDistance(750.0);
|
||||
@@ -456,10 +423,8 @@ public class CustomContentProviderUtilsTest {
|
||||
statistics.setMaxSpeed(60.0);
|
||||
statistics.setMaxElevation(1250.0);
|
||||
statistics.setMinElevation(1200.0);
|
||||
statistics.setMaxGrade(15.0);
|
||||
statistics.setMinGrade(-25.0);
|
||||
|
||||
track.setTripStatistics(statistics);
|
||||
track.setTrackStatistics(statistics);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
|
||||
@@ -548,7 +513,7 @@ public class CustomContentProviderUtilsTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(Location[], int, long)}.
|
||||
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], int, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testBulkInsertTrackPoint() {
|
||||
@@ -557,9 +522,9 @@ public class CustomContentProviderUtilsTest {
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), -1, trackId);
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getTrackPoints().toArray(new TrackPoint[0]), -1, trackId);
|
||||
Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), 8, trackId);
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getTrackPoints().toArray(new TrackPoint[0]), 8, trackId);
|
||||
Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
}
|
||||
|
||||
@@ -615,16 +580,16 @@ public class CustomContentProviderUtilsTest {
|
||||
byte[] sensor = "Sensor state".getBytes();
|
||||
when(cursorMock.getBlob(index++)).thenReturn(sensor);
|
||||
|
||||
Location location = contentProviderUtils.createTrackPoint(cursorMock);
|
||||
Assert.assertEquals((double) longitude, location.getLongitude(), 0.01);
|
||||
Assert.assertEquals((double) latitude, location.getLatitude(), 0.01);
|
||||
TrackPoint location = contentProviderUtils.createTrackPoint(cursorMock);
|
||||
Assert.assertEquals(longitude, location.getLongitude(), 0.01);
|
||||
Assert.assertEquals(latitude, location.getLatitude(), 0.01);
|
||||
Assert.assertEquals(time, location.getTime(), 0.01);
|
||||
Assert.assertEquals(speed, location.getSpeed(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method
|
||||
* {@link ContentProviderUtils#insertTrackPoint(Location, long)}.
|
||||
* {@link ContentProviderUtils#insertTrackPoint(TrackPoint, long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testInsertTrackPoint() {
|
||||
@@ -633,7 +598,7 @@ public class CustomContentProviderUtilsTest {
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
contentProviderUtils.insertTrackPoint(TestDataUtil.createLocation(22), trackId);
|
||||
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
|
||||
Assert.assertEquals(11, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
|
||||
}
|
||||
|
||||
@@ -647,8 +612,8 @@ public class CustomContentProviderUtilsTest {
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
insertTrackWithLocations(track);
|
||||
|
||||
Location lastLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
checkLocation(9, lastLocation);
|
||||
TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
checkLocation(9, lastTrackPoint.getLocation());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -661,9 +626,9 @@ public class CustomContentProviderUtilsTest {
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
long[] trackpointIds = new long[track.getTrackPoints().size()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
|
||||
}
|
||||
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
|
||||
@@ -680,9 +645,9 @@ public class CustomContentProviderUtilsTest {
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
long[] trackpointIds = new long[track.getTrackPoints().size()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
|
||||
}
|
||||
|
||||
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
|
||||
@@ -690,7 +655,7 @@ public class CustomContentProviderUtilsTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, TrackPointFactory)} in descending.
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in descending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointLocationIterator_desc() {
|
||||
@@ -699,25 +664,25 @@ public class CustomContentProviderUtilsTest {
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
long[] trackpointIds = new long[track.getTrackPoints().size()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
|
||||
}
|
||||
|
||||
long startTrackPointId = trackpointIds[9];
|
||||
|
||||
TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true);
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
Assert.assertTrue(locationIterator.hasNext());
|
||||
Location location = locationIterator.next();
|
||||
Assert.assertEquals(startTrackPointId - i, locationIterator.getTrackPointId());
|
||||
checkLocation((trackpointIds.length - 1) - i, location);
|
||||
Assert.assertTrue(trackPointIterator.hasNext());
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
Assert.assertEquals(startTrackPointId - i, trackPointIterator.getTrackPointId());
|
||||
checkLocation((trackpointIds.length - 1) - i, trackPoint.getLocation());
|
||||
}
|
||||
Assert.assertFalse(locationIterator.hasNext());
|
||||
Assert.assertFalse(trackPointIterator.hasNext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, TrackPointFactory)} in ascending.
|
||||
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in ascending.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTrackPointLocationIterator_asc() {
|
||||
@@ -726,20 +691,20 @@ public class CustomContentProviderUtilsTest {
|
||||
Track track = TestDataUtil.getTrack(trackId, 10);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
|
||||
long[] trackpointIds = new long[track.getNumberOfPoints()];
|
||||
long[] trackpointIds = new long[track.getTrackPoints().size()];
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getLocations().get(i), track.getId()));
|
||||
trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.getTrackPoints().get(i), track.getId()));
|
||||
}
|
||||
|
||||
long startTrackPointId = trackpointIds[0];
|
||||
|
||||
TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY);
|
||||
TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false);
|
||||
for (int i = 0; i < trackpointIds.length; i++) {
|
||||
Assert.assertTrue(locationIterator.hasNext());
|
||||
Location location = locationIterator.next();
|
||||
TrackPoint trackPoint = locationIterator.next();
|
||||
Assert.assertEquals(startTrackPointId + i, locationIterator.getTrackPointId());
|
||||
|
||||
checkLocation(i, location);
|
||||
checkLocation(i, trackPoint.getLocation());
|
||||
}
|
||||
Assert.assertFalse(locationIterator.hasNext());
|
||||
}
|
||||
@@ -764,7 +729,7 @@ public class CustomContentProviderUtilsTest {
|
||||
*/
|
||||
private void insertTrackWithLocations(Track track) {
|
||||
contentProviderUtils.insertTrack(track);
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getTrackPoints().toArray(new TrackPoint[0]), track.getTrackPoints().size(), track.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CustomSQLiteOpenHelperTest {
|
||||
|
||||
private static final String TRACKS_CREATE_TABLE_V23 = "CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)";
|
||||
private static final String TRACKPOINTS_CREATE_TABLE_V23 = "CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT)";
|
||||
private static final String WAYPOINTS_CREATE_TABLE_V23 = "CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, type INTEGER, length FLOAT, duration INTEGER, starttime INTEGER, startid INTEGER, stopid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, photoUrl TEXT)";
|
||||
|
||||
private static final String DATABASE_NAME = "test.db";
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
/**
|
||||
* Get the SQL create statements for all tables (ordered by name).
|
||||
*
|
||||
* @return Map(TableName, SQL)
|
||||
*/
|
||||
private static Map<String, String> getTableSQL(SQLiteDatabase db) {
|
||||
HashMap<String, String> tableSQL = new HashMap<>();
|
||||
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + WaypointsColumns.TABLE_NAME + "')", null, null, null, "name")) {
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
tableSQL.put(cursor.getString(0), cursor.getString(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return tableSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL create statements for all indices (ordered by name).
|
||||
*
|
||||
* @return Map(tableName, SQL)
|
||||
*/
|
||||
public static Map<String, String> getIndexSQL(SQLiteDatabase db) {
|
||||
HashMap<String, String> indexSQL = new HashMap<>();
|
||||
try (Cursor cursor = db.rawQuery("SELECT tbl_name, SQL FROM sqlite_master WHERE type = 'index'", null)) {
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
indexSQL.put(cursor.getString(0), cursor.getString(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return indexSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a SQL create statement is in the schema.
|
||||
*
|
||||
* @param sqlCreate the table name
|
||||
*/
|
||||
private static boolean hasSqlCreate(SQLiteDatabase db, String sqlCreate) {
|
||||
try (Cursor cursor = db.rawQuery("SELECT SQL FROM sqlite_master", null)) {
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
String sql = cursor.getString(0);
|
||||
if (sqlCreate.equals(sql)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context.deleteDatabase(DATABASE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCreate() {
|
||||
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) {
|
||||
Assert.assertTrue(hasSqlCreate(db, TracksColumns.CREATE_TABLE));
|
||||
|
||||
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE));
|
||||
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE_INDEX));
|
||||
|
||||
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE));
|
||||
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE_INDEX));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onUpgrade_FromVersion23() {
|
||||
// Manually create database schema with version 23 (base version)
|
||||
{
|
||||
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
}
|
||||
}.getWritableDatabase();
|
||||
|
||||
dbBase.execSQL(TRACKS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(TRACKPOINTS_CREATE_TABLE_V23);
|
||||
dbBase.execSQL(WAYPOINTS_CREATE_TABLE_V23);
|
||||
|
||||
dbBase.close();
|
||||
}
|
||||
|
||||
// Open database with SQL upgrade
|
||||
Map<String, String> tableByUpgrade;
|
||||
Map<String, String> indicesByUpgrade;
|
||||
try (SQLiteDatabase dbUpgraded = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
|
||||
tableByUpgrade = getTableSQL(dbUpgraded);
|
||||
indicesByUpgrade = getIndexSQL(dbUpgraded);
|
||||
}
|
||||
|
||||
// Open database via
|
||||
context.deleteDatabase(DATABASE_NAME);
|
||||
|
||||
Map<String, String> tablesByCreate;
|
||||
Map<String, String> indicesByCreate;
|
||||
try (SQLiteDatabase dbCreated = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase()) {
|
||||
tablesByCreate = getTableSQL(dbCreated);
|
||||
indicesByCreate = getIndexSQL(dbCreated);
|
||||
}
|
||||
|
||||
// then - verify table structure
|
||||
Assert.assertEquals(3, tableByUpgrade.size());
|
||||
Assert.assertEquals(tableByUpgrade.size(), tablesByCreate.size());
|
||||
|
||||
Assert.assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME));
|
||||
Assert.assertEquals(tablesByCreate.get(TrackPointsColumns.TABLE_NAME), tableByUpgrade.get(TrackPointsColumns.TABLE_NAME));
|
||||
Assert.assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
|
||||
|
||||
// then - verify custom indices
|
||||
Assert.assertEquals(2, indicesByCreate.size());
|
||||
Assert.assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
|
||||
Assert.assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onDowngrade_ToVersion23() {
|
||||
// Create most recent database schema
|
||||
new CustomSQLiteOpenHelper(context, DATABASE_NAME).getReadableDatabase().close();
|
||||
|
||||
// Downgrade schema to version 23 (base version)
|
||||
Map<String, String> tablesByDowngrade;
|
||||
Map<String, String> indicesByDowngrade;
|
||||
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 23).getReadableDatabase()) {
|
||||
tablesByDowngrade = getTableSQL(db);
|
||||
indicesByDowngrade = getIndexSQL(db);
|
||||
}
|
||||
|
||||
// then - verify table structure
|
||||
Assert.assertEquals(TRACKS_CREATE_TABLE_V23, tablesByDowngrade.get("tracks"));
|
||||
Assert.assertEquals(TRACKPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("trackpoints"));
|
||||
Assert.assertEquals(WAYPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("waypoints"));
|
||||
|
||||
// then - verify custom indices
|
||||
Assert.assertEquals(0, indicesByDowngrade.size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
public class ShareContentProviderTest {
|
||||
|
||||
@Test
|
||||
public void testCreateandParseURI() {
|
||||
long[] trackIds = {1, 3, 5};
|
||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
|
||||
|
||||
Assert.assertArrayEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateURIescapeFilename() {
|
||||
long[] trackIds = {1};
|
||||
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_ONLY_TRACK);
|
||||
|
||||
Assert.assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_only_track/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.TrackStubUtils;
|
||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
@@ -63,287 +64,269 @@ public class ChartFragmentTest {
|
||||
boolean chartByDistance = false;
|
||||
chartFragment = (ChartFragment) ChartFragment.newInstance(chartByDistance);
|
||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
||||
chartFragment.setTripStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
||||
chartFragment.setTrackStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_sensorIncorrect() {
|
||||
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
|
||||
public void testCreatePendingPoint_sensorIncorrect() {
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
// No input.
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
|
||||
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
|
||||
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
|
||||
|
||||
// Input incorrect state.
|
||||
// Creates SensorData.
|
||||
SensorDataSet sensorDataSet = new SensorDataSet(SensorDataSet.DATA_UNAVAILABLE, SensorDataSet.DATA_UNAVAILABLE);
|
||||
sensorDataSetLocation.setSensorDataSet(sensorDataSet);
|
||||
trackPoint.setSensorDataSet(sensorDataSet);
|
||||
// Test.
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01);
|
||||
point = chartFragment.createPendingPoint(trackPoint);
|
||||
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
|
||||
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
|
||||
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the correct values of sensor in {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
* Tests the logic to get the correct values of sensor in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_sensorCorrect() {
|
||||
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
|
||||
public void testCreatePendingPoint_sensorCorrect() {
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
// No input.
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.CADENCE_SERIES + 1], 0.01);
|
||||
Assert.assertEquals(Float.NaN, point[ChartView.POWER_SERIES + 1], 0.01);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
|
||||
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
|
||||
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
|
||||
|
||||
// Creates SensorData.
|
||||
SensorDataSet sensorDataSet = new SensorDataSet(100, 101, 102);
|
||||
|
||||
// Creates SensorDataSet.
|
||||
sensorDataSetLocation.setSensorDataSet(sensorDataSet);
|
||||
trackPoint.setSensorDataSet(sensorDataSet);
|
||||
// Test.
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation);
|
||||
Assert.assertEquals(100.0, point[ChartView.HEART_RATE_SERIES + 1], 0.01);
|
||||
Assert.assertEquals(101.0, point[ChartView.CADENCE_SERIES + 1], 0.01);
|
||||
Assert.assertEquals(102.0, point[ChartView.POWER_SERIES + 1], 0.01);
|
||||
point = chartFragment.createPendingPoint(trackPoint);
|
||||
Assert.assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||
Assert.assertEquals(101.0, point.getCadence(), 0.01);
|
||||
Assert.assertEquals(102.0, point.getPower(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of metric Distance in {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
* Tests the logic to get the value of metric Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_distanceMetric() {
|
||||
// By distance.
|
||||
public void testCreatePendingPoint_distanceMetric() {
|
||||
chartFragment.setChartByDistance(true);
|
||||
// Resets last location and writes first location.
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[0], 0.01);
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The second is a same location, just different time.
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
Assert.assertEquals(0.0, point[0], 0.01);
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The third location is a new location, and use metric.
|
||||
TrackPoint sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation3.setLatitude(23);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation3);
|
||||
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint3.setLatitude(23);
|
||||
point = chartFragment.createPendingPoint(trackPoint3);
|
||||
|
||||
// Computes the distance between Latitude 22 and 23.
|
||||
float[] results = new float[4];
|
||||
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(),
|
||||
sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
|
||||
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(),
|
||||
trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
|
||||
double distance1 = results[0] * UnitConversions.M_TO_KM;
|
||||
Assert.assertEquals(distance1, point[0], 0.01);
|
||||
Assert.assertEquals(distance1, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The fourth location is a new location, and use metric.
|
||||
TrackPoint sensorDataSetLocation4 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation4.setLatitude(24);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation4);
|
||||
TrackPoint trackPoint4 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint4.setLatitude(24);
|
||||
point = chartFragment.createPendingPoint(trackPoint4);
|
||||
|
||||
// Computes the distance between Latitude 23 and 24.
|
||||
Location.distanceBetween(sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(),
|
||||
sensorDataSetLocation4.getLatitude(), sensorDataSetLocation4.getLongitude(), results);
|
||||
Location.distanceBetween(trackPoint3.getLatitude(), trackPoint3.getLongitude(),
|
||||
trackPoint4.getLatitude(), trackPoint4.getLongitude(), results);
|
||||
double distance2 = results[0] * UnitConversions.M_TO_KM;
|
||||
Assert.assertEquals((distance1 + distance2), point[0], 0.01);
|
||||
Assert.assertEquals((distance1 + distance2), point.getTimeOrDistance(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of imperial Distance in {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
* Tests the logic to get the value of imperial Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_distanceImperial() {
|
||||
public void testCreatePendingPoint_distanceImperial() {
|
||||
// By distance.
|
||||
chartFragment.setChartByDistance(true);
|
||||
// Setups to use imperial.
|
||||
chartFragment.setMetricUnits(false);
|
||||
|
||||
// The first is a same location, just different time.
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[0], 0.01);
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The second location is a new location, and use imperial.
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation2.setLatitude(23);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setLatitude(23);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 22 and 23.
|
||||
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
*/
|
||||
float[] results = new float[4];
|
||||
Location.distanceBetween(sensorDataSetLocation1.getLatitude(), sensorDataSetLocation1.getLongitude(), sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), results);
|
||||
Location.distanceBetween(trackPoint1.getLatitude(), trackPoint1.getLongitude(), trackPoint2.getLatitude(), trackPoint2.getLongitude(), results);
|
||||
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
Assert.assertEquals(distance1, point[0], 0.01);
|
||||
Assert.assertEquals(distance1, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The third location is a new location, and use imperial.
|
||||
TrackPoint sensorDataSetLocation3 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation3.setLatitude(24);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation3);
|
||||
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint3.setLatitude(24);
|
||||
point = chartFragment.createPendingPoint(trackPoint3);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 23 and 24.
|
||||
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
*/
|
||||
Location.distanceBetween(sensorDataSetLocation2.getLatitude(), sensorDataSetLocation2.getLongitude(), sensorDataSetLocation3.getLatitude(), sensorDataSetLocation3.getLongitude(), results);
|
||||
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(), trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
|
||||
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
Assert.assertEquals(distance1 + distance2, point[0], 0.01);
|
||||
Assert.assertEquals(distance1 + distance2, point.getTimeOrDistance(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the values of time in {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
* Tests the logic to get the values of time in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_time() {
|
||||
// By time
|
||||
public void testCreatePendingPoint_time() {
|
||||
chartFragment.setChartByDistance(false);
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[0], 0.01);
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
long timeSpan = 222;
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + timeSpan);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
Assert.assertEquals((double) timeSpan, point[0], 0.01);
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setTime(trackPoint1.getTime() + timeSpan);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
Assert.assertEquals((double) timeSpan, point.getTimeOrDistance(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of elevation in {@link ChartFragment#fillDataPoint(Location, double[])} by one and two points.
|
||||
* Tests the logic to get the value of elevation in {@link ChartFragment#createPendingPoint(TrackPoint)} by one and two points.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_elevation() {
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
public void testCreatePendingPoint_elevation() {
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* At first, clear old points of elevation, so give true to the second parameter.
|
||||
* Then only one value INITIAL_ALTITUDE in buffer.
|
||||
*/
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[ChartView.ELEVATION_SERIES + 1], 0.01);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point.getElevation(), 0.01);
|
||||
|
||||
/*
|
||||
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
|
||||
*/
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0,
|
||||
point[ChartView.ELEVATION_SERIES + 1], 0.01);
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point.getElevation(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of speed in {@link ChartFragment#fillDataPoint(Location, double[])}.
|
||||
* Tests the logic to get the value of speed in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
* In this test, firstly remove all points in memory, and then fill in two points one by one.
|
||||
* The speed values of these points are 129, 130.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_speed() {
|
||||
public void testCreatePendingPoint_speed() {
|
||||
/*
|
||||
* At first, clear old points of speed, so give true to the second parameter.
|
||||
* It will not be filled in to the speed buffer.
|
||||
*/
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation1.setSpeed(128.5f);
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setSpeed(128.5f);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
Assert.assertEquals(0.0, point.getSpeed(), 0.01);
|
||||
|
||||
/*
|
||||
* Tests the logic when both metricUnits and reportSpeed are true.
|
||||
* This location will be filled into speed buffer.
|
||||
*/
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
|
||||
*/
|
||||
sensorDataSetLocation2.setTime(sensorDataSetLocation1.getTime() + 222);
|
||||
sensorDataSetLocation2.setSpeed(130);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||
trackPoint2.setTime(trackPoint1.getTime() + 222);
|
||||
trackPoint2.setSpeed(130);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point.getSpeed(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to compute speed when use Imperial.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_speedImperial() {
|
||||
// Setups to use imperial.
|
||||
public void testCreatePendingPoint_speedImperial() {
|
||||
chartFragment.setMetricUnits(false);
|
||||
|
||||
// First data point is not added to the speed buffer
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation1.setSpeed(100.0f);
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setSpeed(100.0f);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
Assert.assertEquals(0.0, point.getSpeed(), 0.01);
|
||||
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
|
||||
sensorDataSetLocation2.setSpeed(102);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||
trackPoint2.setTime(trackPoint2.getTime() + 222);
|
||||
trackPoint2.setSpeed(102);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get pace value when reportSpeed is false.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_pace_nonZeroSpeed() {
|
||||
// Setups reportSpeed to false.
|
||||
public void testCreatePendingPoint_pace_nonZeroSpeed() {
|
||||
chartFragment.setReportSpeed(false);
|
||||
|
||||
// First data point is not added to the speed buffer
|
||||
TrackPoint sensorDataSetLocation1 = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation1.setSpeed(100.0f);
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation1);
|
||||
Assert.assertEquals(0.0, point[ChartView.SPEED_SERIES + 1], 0.01);
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setSpeed(100.0f);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
Assert.assertEquals(0.0, point.getSpeed(), 0.01);
|
||||
|
||||
TrackPoint sensorDataSetLocation2 = TrackStubUtils.createSensorDataSetLocation();
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
sensorDataSetLocation2.setTime(sensorDataSetLocation2.getTime() + 222);
|
||||
sensorDataSetLocation2.setSpeed(102);
|
||||
point = fillDataPointTestHelper(sensorDataSetLocation2);
|
||||
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point[ChartView.PACE_SERIES + 1], 0.01);
|
||||
trackPoint2.setTime(trackPoint2.getTime() + 222);
|
||||
trackPoint2.setSpeed(102);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point.getPace(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get pace value when reportSpeed is false and average speed is zero.
|
||||
*/
|
||||
@Test
|
||||
public void testFillDataPoint_pace_zeroSpeed() {
|
||||
// Setups reportSpeed to false.
|
||||
public void testCreatePendingPoint_pace_zeroSpeed() {
|
||||
chartFragment.setReportSpeed(false);
|
||||
TrackPoint sensorDataSetLocation = TrackStubUtils.createSensorDataSetLocation();
|
||||
sensorDataSetLocation.setSpeed(0);
|
||||
double[] point = fillDataPointTestHelper(sensorDataSetLocation);
|
||||
Assert.assertEquals(0.0, point[ChartView.PACE_SERIES + 1], 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to test fillDataPoint.
|
||||
*
|
||||
* @param location location to fill
|
||||
* @return data of this location
|
||||
*/
|
||||
private double[] fillDataPointTestHelper(Location location) {
|
||||
double[] point = new double[ChartView.NUM_SERIES + 1];
|
||||
chartFragment.fillDataPoint(location, point);
|
||||
return point;
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setSpeed(0);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
Assert.assertEquals(0.0, point.getPace(), 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.Locale;
|
||||
import java.util.SimpleTimeZone;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
@@ -83,24 +84,24 @@ public abstract class AbstractTestFileTrackImporter {
|
||||
@Mock
|
||||
public ContentProviderUtils contentProviderUtils;
|
||||
|
||||
Location createLocation(int index, long time) {
|
||||
Location location = new Location(LocationManager.GPS_PROVIDER);
|
||||
location.setLatitude(TRACK_LATITUDE + index);
|
||||
location.setLongitude(TRACK_LONGITUDE + index);
|
||||
location.setAltitude(TRACK_ELEVATION + index);
|
||||
location.setTime(time);
|
||||
return location;
|
||||
TrackPoint createTrackPoint(int index, long time) {
|
||||
Location trackPoint = new Location(LocationManager.GPS_PROVIDER);
|
||||
trackPoint.setLatitude(TRACK_LATITUDE + index);
|
||||
trackPoint.setLongitude(TRACK_LONGITUDE + index);
|
||||
trackPoint.setAltitude(TRACK_ELEVATION + index);
|
||||
trackPoint.setTime(time);
|
||||
return new TrackPoint(trackPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects the first track point to be added.
|
||||
*
|
||||
* @param location the location
|
||||
* @param trackPoint the trackPoint
|
||||
* @param trackId the track id
|
||||
* @param trackPointId the track point id
|
||||
*/
|
||||
protected void expectFirstTrackPoint(Location location, long trackId, long trackPointId) {
|
||||
when(contentProviderUtils.bulkInsertTrackPoint(location != null ? (Location[]) any() : (Location[]) any(), eq(1), eq(trackId))).thenReturn(1);
|
||||
protected void expectFirstTrackPoint(TrackPoint trackPoint, long trackId, long trackPointId) {
|
||||
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(1), eq(trackId))).thenReturn(1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,7 +125,7 @@ public abstract class AbstractTestFileTrackImporter {
|
||||
Assert.assertEquals(name, track.getName());
|
||||
Assert.assertEquals(description, track.getDescription());
|
||||
if (time != -1L) {
|
||||
Assert.assertEquals(time, track.getTripStatistics().getStartTime());
|
||||
Assert.assertEquals(time, track.getTrackStatistics().getStartTime_ms());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -21,10 +20,12 @@ import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -32,23 +33,23 @@ import static org.junit.Assert.assertNotNull;
|
||||
/**
|
||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||
* <p>
|
||||
* TODO: test ignores {@link de.dennisguse.opentracks.stats.TripStatistics} for now.
|
||||
* TODO: test ignores {@link TrackStatistics} for now.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class ExportImportTest {
|
||||
|
||||
private static final String TAG = ExportImportTest.class.getSimpleName();
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
private static final String TRACK_ICON = "the track icon";
|
||||
private static final String TRACK_CATEGORY = "the category";
|
||||
private static final String TRACK_DESCRIPTION = "the description";
|
||||
private final List<Waypoint> waypoints = new ArrayList<>();
|
||||
private long importTrackId;
|
||||
private long trackId = System.currentTimeMillis();
|
||||
private final long trackId = System.currentTimeMillis();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -57,17 +58,16 @@ public class ExportImportTest {
|
||||
track.setCategory(TRACK_CATEGORY);
|
||||
track.setDescription(TRACK_DESCRIPTION);
|
||||
contentProviderUtils.insertTrack(track);
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getLocations().toArray(new Location[0]), track.getLocations().size(), track.getId());
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.getTrackPoints().toArray(new TrackPoint[0]), track.getTrackPoints().size(), track.getId());
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Waypoint waypoint = new Waypoint();
|
||||
Waypoint waypoint = new Waypoint(track.getTrackPoints().get(i).getLocation());
|
||||
waypoint.setName("the waypoint " + i);
|
||||
waypoint.setDescription("the waypoint description " + i);
|
||||
waypoint.setCategory("the waypoint category" + i);
|
||||
waypoint.setIcon("the waypoing icon" + i);
|
||||
waypoint.setPhotoUrl("the photo url" + i);
|
||||
waypoint.setTrackId(trackId);
|
||||
waypoint.setLocation(track.getLocations().get(i));
|
||||
contentProviderUtils.insertWaypoint(waypoint);
|
||||
|
||||
waypoints.add(waypoint);
|
||||
@@ -114,7 +114,7 @@ public class ExportImportTest {
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals(track.getLocations(), importedTrack.getLocations());
|
||||
assertEquals(track.getTrackPoints(), importedTrack.getTrackPoints());
|
||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
@@ -184,7 +184,7 @@ public class ExportImportTest {
|
||||
// 1. track
|
||||
Track trackImported = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(trackImported);
|
||||
assertEquals(track.getLocations(), trackImported.getLocations());
|
||||
assertEquals(track.getTrackPoints(), trackImported.getTrackPoints());
|
||||
assertEquals(track.getCategory(), trackImported.getCategory());
|
||||
assertEquals(track.getDescription(), trackImported.getDescription());
|
||||
assertEquals(track.getName(), trackImported.getName());
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -28,6 +26,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
@@ -74,9 +73,9 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
String longitude = Double.toString(TRACK_LONGITUDE + index);
|
||||
String elevation = Double.toString(TRACK_ELEVATION + index);
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("<trkpt lat=\"" + latitude + "\" lon=\"" + longitude + "\"><ele>" + elevation + "</ele>");
|
||||
buffer.append("<trkpt lat=\"").append(latitude).append("\" lon=\"").append(longitude).append("\"><ele>").append(elevation).append("</ele>");
|
||||
if (time != null) {
|
||||
buffer.append("<time>" + time + "</time>");
|
||||
buffer.append("<time>").append(time).append("</time>");
|
||||
}
|
||||
buffer.append("</trkpt>");
|
||||
return buffer.toString();
|
||||
@@ -85,14 +84,14 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
@Test
|
||||
public void testOneTrackOneSegment() throws Exception {
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
TrackPoint trackPoint1 = createTrackPoint(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
|
||||
@@ -109,19 +108,19 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneTrackTwoSegments() throws Exception {
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
@@ -141,7 +140,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
|
||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTrackStatistics().getTotalTime());
|
||||
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
}
|
||||
@@ -153,7 +152,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
@@ -169,7 +168,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
|
||||
Assert.assertEquals(TRACK_ID_0, trackId);
|
||||
|
||||
Assert.assertEquals(0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
Assert.assertEquals(0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
|
||||
}
|
||||
|
||||
@@ -207,7 +206,7 @@ public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
|
||||
|
||||
// For the following, use StubReturn since we don't care whether they are invoked or not.
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), anyInt(), anyLong())).thenReturn(1);
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), anyInt(), anyLong())).thenReturn(1);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -28,6 +26,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
@@ -69,14 +68,14 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
@Test
|
||||
public void testOneTrackOneSegment() throws Exception {
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
TrackPoint trackPointl1 = createTrackPoint(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||
|
||||
contentProviderUtils.clearTrack(context, TRACK_ID_0);
|
||||
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(1), eq(TRACK_ID_0))).thenReturn(1);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
@@ -94,20 +93,20 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
|
||||
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneTrackTwoSegments() throws Exception {
|
||||
// given
|
||||
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
|
||||
contentProviderUtils.clearTrack(context, TRACK_ID_0);
|
||||
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||
|
||||
// A flush happens at the end
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((Location[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(5), eq(TRACK_ID_0))).thenReturn(5);
|
||||
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
|
||||
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
|
||||
|
||||
@@ -127,7 +126,7 @@ public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
|
||||
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
|
||||
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTripStatistics().getTotalTime());
|
||||
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTrackStatistics().getTotalTime());
|
||||
|
||||
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -12,6 +11,8 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -21,10 +22,10 @@ import static org.mockito.Mockito.when;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TrackRecordingServiceNotificationManagerTest {
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private Location locationMock;
|
||||
private TrackPoint locationMock;
|
||||
|
||||
@Mock
|
||||
private NotificationCompat.Builder notificationCompatBuilder;
|
||||
@@ -42,10 +43,10 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||
|
||||
// when
|
||||
subject.updateLocation(context, locationMock, 100);
|
||||
subject.updateLocation(context, locationMock, 100);
|
||||
subject.updateLocation(context, locationMock, 1000);
|
||||
subject.updateLocation(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, locationMock, 1000);
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
|
||||
// then
|
||||
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
|
||||
|
||||
@@ -46,7 +46,7 @@ import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -66,7 +66,7 @@ public class TrackRecordingServiceTest {
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private final long trackId = Math.abs(new Random().nextLong());
|
||||
@@ -193,9 +193,9 @@ public class TrackRecordingServiceTest {
|
||||
Track dummyTrack = new Track();
|
||||
dummyTrack.setId(id);
|
||||
dummyTrack.setName("Dummy Track");
|
||||
TripStatistics tripStatistics = new TripStatistics();
|
||||
tripStatistics.setStopTime(stopTime);
|
||||
dummyTrack.setTripStatistics(tripStatistics);
|
||||
TrackStatistics trackStatistics = new TrackStatistics();
|
||||
trackStatistics.setStopTime_ms(stopTime);
|
||||
dummyTrack.setTrackStatistics(trackStatistics);
|
||||
addTrack(dummyTrack, isRecording);
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ public class TrackRecordingServiceTest {
|
||||
location.setSpeed(10);
|
||||
location.setTime(System.currentTimeMillis());
|
||||
location.setBearing(3.0f);
|
||||
trackRecordingService.insertTrackPoint(location);
|
||||
trackRecordingService.insertLocation(location);
|
||||
|
||||
Thread.sleep(200);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -45,7 +45,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@@ -215,7 +215,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
location.setSpeed(10);
|
||||
location.setTime(startTime + i * 10000);
|
||||
location.setBearing(3.0f);
|
||||
service.insertTrackPoint(location);
|
||||
service.insertLocation(location);
|
||||
|
||||
if (i % 7 == 0) {
|
||||
service.insertWaypoint(null, null, null, null);
|
||||
@@ -229,9 +229,9 @@ public class TrackRecordingServiceTestLooper {
|
||||
track = contentProviderUtils.getTrack(id);
|
||||
Assert.assertNotNull(track);
|
||||
Assert.assertEquals(id, track.getId());
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
Assert.assertNotNull(tripStatistics);
|
||||
Assert.assertTrue(tripStatistics.getStartTime() > 0);
|
||||
Assert.assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
Assert.assertNotNull(trackStatistics);
|
||||
Assert.assertTrue(trackStatistics.getStartTime_ms() > 0);
|
||||
Assert.assertTrue(trackStatistics.getStopTime_ms() >= trackStatistics.getStartTime_ms());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.junit.runner.RunWith;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AnnouncementPeriodicTaskFactoryTest {
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
|
||||
@@ -22,28 +22,28 @@ import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/**
|
||||
* Tests for {@link TripStatistics}.
|
||||
* Tests for {@link TrackStatistics}.
|
||||
* This only tests non-trivial pieces of that class.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class TripStatisticsTest {
|
||||
public class TrackStatisticsTest {
|
||||
|
||||
private TripStatistics statistics;
|
||||
private TrackStatistics statistics;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
statistics = new TripStatistics();
|
||||
statistics = new TrackStatistics();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMerge() {
|
||||
TripStatistics statistics2 = new TripStatistics();
|
||||
statistics.setStartTime(1000L); // Resulting start time
|
||||
statistics.setStopTime(2500L);
|
||||
statistics2.setStartTime(3000L);
|
||||
statistics2.setStopTime(4000L); // Resulting stop time
|
||||
TrackStatistics statistics2 = new TrackStatistics();
|
||||
statistics.setStartTime_ms(1000L); // Resulting start time
|
||||
statistics.setStopTime_ms(2500L);
|
||||
statistics2.setStartTime_ms(3000L);
|
||||
statistics2.setStopTime_ms(4000L); // Resulting stop time
|
||||
statistics.setTotalTime(1500L);
|
||||
statistics2.setTotalTime(1000L); // Result: 1500+1000
|
||||
statistics.setMovingTime(700L);
|
||||
@@ -58,15 +58,11 @@ public class TripStatisticsTest {
|
||||
statistics.setMinElevation(1200.0); // Resulting min elevation
|
||||
statistics2.setMaxElevation(3575.0); // Resulting max elevation
|
||||
statistics2.setMinElevation(2800.0);
|
||||
statistics.setMaxGrade(15.0);
|
||||
statistics.setMinGrade(-25.0); // Resulting min grade
|
||||
statistics2.setMaxGrade(35.0); // Resulting max grade
|
||||
statistics2.setMinGrade(0.0);
|
||||
|
||||
statistics.merge(statistics2);
|
||||
|
||||
Assert.assertEquals(1000L, statistics.getStartTime());
|
||||
Assert.assertEquals(4000L, statistics.getStopTime());
|
||||
Assert.assertEquals(1000L, statistics.getStartTime_ms());
|
||||
Assert.assertEquals(4000L, statistics.getStopTime_ms());
|
||||
Assert.assertEquals(2500L, statistics.getTotalTime());
|
||||
Assert.assertEquals(1300L, statistics.getMovingTime());
|
||||
Assert.assertEquals(1100.0, statistics.getTotalDistance(), 0.001);
|
||||
@@ -74,8 +70,6 @@ public class TripStatisticsTest {
|
||||
Assert.assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed(), 0.001);
|
||||
Assert.assertEquals(1200.0, statistics.getMinElevation(), 0.001);
|
||||
Assert.assertEquals(3575.0, statistics.getMaxElevation(), 0.001);
|
||||
Assert.assertEquals(-25.0, statistics.getMinGrade(), 0.001);
|
||||
Assert.assertEquals(35.0, statistics.getMaxGrade(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -0,0 +1,34 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AnnouncementUtilsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void getAnnouncement() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats);
|
||||
|
||||
// then
|
||||
Assert.assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Tests for {@link FileUtils}.
|
||||
@@ -108,9 +109,7 @@ public class FileUtilsTest {
|
||||
File directory = new File("/dir1/dir2/");
|
||||
String suffix = ".gpx";
|
||||
char[] name = new char[FileUtils.MAX_FAT32_PATH_LENGTH];
|
||||
for (int i = 0; i < name.length; i++) {
|
||||
name[i] = 'a';
|
||||
}
|
||||
Arrays.fill(name, 'a');
|
||||
String nameString = new String(name);
|
||||
String truncated = FileUtils.truncateFileName(directory, nameString, suffix);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ import java.util.TimeZone;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class StringUtilsTest {
|
||||
|
||||
private Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,8 +16,8 @@ limitations under the License.
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.dennisguse.opentracks"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="3287"
|
||||
android:versionName="v3.4.0"> <!-- For FDroid. -->
|
||||
android:versionCode="3330"
|
||||
android:versionName="v3.6.0"> <!-- For FDroid. -->
|
||||
<!-- Permission to initialize services -->
|
||||
<application
|
||||
android:name=".Startup"
|
||||
@@ -31,7 +31,9 @@ limitations under the License.
|
||||
<!-- Activities -->
|
||||
<activity android:name=".AboutActivity" />
|
||||
|
||||
<activity android:name=".TrackDeleteActivity" />
|
||||
<activity
|
||||
android:name=".TrackDeleteActivity"
|
||||
android:theme="@style/ThemeCustomTransparent" />
|
||||
|
||||
<activity
|
||||
android:name=".HelpActivity"
|
||||
|
||||
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private final long[] trackIds;
|
||||
private final Context context;
|
||||
|
||||
@@ -16,18 +16,38 @@
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
|
||||
/**
|
||||
* An activity to add/edit a marker.
|
||||
@@ -39,16 +59,27 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
private static final int CAMERA_REQUEST_CODE = 5;
|
||||
private static final int GALLERY_IMG_REQUEST_CODE = 7;
|
||||
|
||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||
private long trackId;
|
||||
private long markerId;
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private Waypoint waypoint;
|
||||
|
||||
private MenuItem insertPhotoMenuItem;
|
||||
private MenuItem insertGalleryImgMenuItem;
|
||||
|
||||
private Uri photoUri;
|
||||
private boolean hasCamera;
|
||||
|
||||
// UI elements
|
||||
private EditText waypointName;
|
||||
private AutoCompleteTextView waypointMarkerType;
|
||||
private EditText waypointDescription;
|
||||
private ImageView waypointPhoto;
|
||||
private ImageView waypointDeletePhotoBtn;
|
||||
private Button done;
|
||||
|
||||
@Override
|
||||
@@ -57,7 +88,9 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
|
||||
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
|
||||
|
||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||
|
||||
// Setup UI elements
|
||||
waypointName = findViewById(R.id.marker_edit_waypoint_name);
|
||||
@@ -65,6 +98,19 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.waypoint_types, android.R.layout.simple_dropdown_item_1line);
|
||||
waypointMarkerType.setAdapter(adapter);
|
||||
waypointDescription = findViewById(R.id.marker_edit_waypoint_description);
|
||||
waypointPhoto = findViewById(R.id.marker_edit_waypoint_photo);
|
||||
|
||||
waypointDeletePhotoBtn = findViewById(R.id.marker_edit_waypoint_photo_delete);
|
||||
waypointDeletePhotoBtn.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (waypoint != null && waypoint.hasPhoto())
|
||||
waypoint.setPhotoUrl(null);
|
||||
waypointPhoto.setImageBitmap(null);
|
||||
photoUri = null;
|
||||
hideAndShowOptions();
|
||||
}
|
||||
});
|
||||
|
||||
Button cancel = findViewById(R.id.marker_edit_cancel);
|
||||
cancel.setOnClickListener(new OnClickListener() {
|
||||
@@ -94,6 +140,64 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
return R.layout.marker_edit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.marker_edit, menu);
|
||||
|
||||
insertPhotoMenuItem = menu.findItem(R.id.marker_edit_insert_photo);
|
||||
insertPhotoMenuItem.setVisible(hasCamera);
|
||||
insertGalleryImgMenuItem = menu.findItem(R.id.marker_edit_insert_gallery_img);
|
||||
hideAndShowOptions();
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.marker_edit_insert_photo:
|
||||
createWaypointWithPicture();
|
||||
return true;
|
||||
case R.id.marker_edit_insert_gallery_img:
|
||||
createWaypointWithGalleryImage();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == CAMERA_REQUEST_CODE) {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
} else if (resultCode == RESULT_OK) {
|
||||
setWaypointImageView(photoUri);
|
||||
}
|
||||
} else if (requestCode == GALLERY_IMG_REQUEST_CODE) {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
} else if (resultCode == RESULT_OK) {
|
||||
Uri srcUri = data.getData();
|
||||
try (ParcelFileDescriptor parcelFd = getContentResolver().openFileDescriptor(srcUri, "r")) {
|
||||
FileDescriptor srcFd = parcelFd.getFileDescriptor();
|
||||
File dstFile = new File(FileUtils.getImageUrl(this, getTrackId()));
|
||||
FileUtils.copy(srcFd, dstFile);
|
||||
|
||||
photoUri = FileUtils.getUriForFile(this, dstFile);
|
||||
setWaypointImageView(photoUri);
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI based on the marker id.
|
||||
*/
|
||||
@@ -133,7 +237,62 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
waypointName.setText(waypoint.getName());
|
||||
waypointMarkerType.setText(waypoint.getCategory());
|
||||
waypointDescription.setText(waypoint.getDescription());
|
||||
if (waypoint.hasPhoto()) {
|
||||
photoUri = waypoint.getPhotoURI();
|
||||
setWaypointImageView(photoUri);
|
||||
}
|
||||
}
|
||||
|
||||
hideAndShowOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and hide/shows all buttons/options about marker photo options.
|
||||
*
|
||||
* If a photo is set then one's options are shown, otherwise another ones are shown.
|
||||
*/
|
||||
private void hideAndShowOptions() {
|
||||
boolean isPhotoSet = (waypoint != null && waypoint.hasPhoto()) || photoUri != null;
|
||||
if (insertPhotoMenuItem != null && insertGalleryImgMenuItem != null) {
|
||||
insertPhotoMenuItem.setVisible(!isPhotoSet);
|
||||
insertGalleryImgMenuItem.setVisible(!isPhotoSet);
|
||||
}
|
||||
waypointDeletePhotoBtn.setVisibility(isPhotoSet ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the trackId. If trackId == -1 then get it from waypoint object.
|
||||
*/
|
||||
private long getTrackId() {
|
||||
return trackId == -1 ? waypoint.getTrackId() : trackId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ImageView waypointPhoto with the uri photo.
|
||||
*
|
||||
* @param uri the uri photo.
|
||||
*/
|
||||
private void setWaypointImageView(Uri uri) {
|
||||
try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r")) {
|
||||
FileDescriptor fd = pfd.getFileDescriptor();
|
||||
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
|
||||
waypointPhoto.setImageBitmap(bitmap);
|
||||
hideAndShowOptions();
|
||||
} catch(IOException e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void createWaypointWithPicture() {
|
||||
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, getTrackId());
|
||||
photoUri = intentAndPhotoUri.second;
|
||||
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
|
||||
}
|
||||
|
||||
private void createWaypointWithGalleryImage() {
|
||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,7 +303,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
waypointName.getText().toString(),
|
||||
waypointMarkerType.getText().toString(),
|
||||
waypointDescription.getText().toString(),
|
||||
null);
|
||||
photoUri != null ? photoUri.toString() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,6 +313,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
waypoint.setName(waypointName.getText().toString());
|
||||
waypoint.setCategory(waypointMarkerType.getText().toString());
|
||||
waypoint.setDescription(waypointDescription.getText().toString());
|
||||
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
||||
|
||||
new ContentProviderUtils(this).updateWaypoint(waypoint);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
|
||||
private static final String[] PROJECTION = new String[]{WaypointsColumns._ID,
|
||||
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
|
||||
WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
|
||||
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
|
||||
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
@@ -179,7 +179,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
new String[]{String.valueOf(track.getId())}, null);
|
||||
} else {
|
||||
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
|
||||
WaypointsColumns.STARTTIME + " IS NULL", null, null);
|
||||
null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
@@ -172,7 +172,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
|
||||
searchEngine = new SearchEngine(contentProviderUtils);
|
||||
|
||||
arrayAdapter = new ArrayAdapter<Map<String, Object>>(this, R.layout.list_item, R.id.list_item_name) {
|
||||
@@ -424,7 +424,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
private Map<String, Object> prepareTrackForDisplay(Track track) {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
String icon = track.getIcon();
|
||||
String category = icon != null && !icon.equals("") ? null : track.getCategory();
|
||||
|
||||
@@ -433,10 +433,10 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
|
||||
resultMap.put(ICON_ID_FIELD, TrackIconUtils.getIconDrawable(icon));
|
||||
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.image_track);
|
||||
resultMap.put(NAME_FIELD, track.getName());
|
||||
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatistics.getTotalTime()));
|
||||
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, tripStatistics.getTotalDistance(), metricUnits));
|
||||
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
|
||||
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, trackStatistics.getTotalDistance(), metricUnits));
|
||||
resultMap.put(MARKER_COUNT_FIELD, contentProviderUtils.getWaypointCount(track.getId()));
|
||||
resultMap.put(START_TIME_FIELD, tripStatistics.getStartTime());
|
||||
resultMap.put(START_TIME_FIELD, trackStatistics.getStartTime_ms());
|
||||
resultMap.put(CATEGORY_FIELD, category);
|
||||
resultMap.put(DESCRIPTION_FIELD, track.getDescription());
|
||||
resultMap.put(PHOTO_URL_FIELD, null);
|
||||
|
||||
@@ -16,16 +16,12 @@
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Pair;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -34,7 +30,6 @@ import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
@@ -50,6 +45,7 @@ import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatsFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
@@ -57,7 +53,7 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
/**
|
||||
* An activity to show the track detail.
|
||||
* An activity to show the track detail, record a new track or resumes an existing one.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
@@ -70,16 +66,8 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
private static final String TAG = TrackDetailActivity.class.getSimpleName();
|
||||
|
||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||
private static final String PHOTO_URI_KEY = "photo_uri_key";
|
||||
private static final String HAS_PHOTO_KEY = "has_photo_key";
|
||||
|
||||
private static final int CAMERA_REQUEST_CODE = 5;
|
||||
private static final int EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 6;
|
||||
|
||||
// The following are set in onCreate
|
||||
private boolean hasCamera;
|
||||
private Uri photoUri;
|
||||
private boolean hasPhoto;
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private SharedPreferences sharedPreferences;
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
@@ -102,12 +90,36 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
@Override
|
||||
public void run() {
|
||||
trackController.update(isRecording(), recordingTrackPaused);
|
||||
if (hasPhoto && photoUri != null) {
|
||||
hasPhoto = false;
|
||||
long markerId = trackRecordingServiceConnection.addMarker(TrackDetailActivity.this, null, null, null, photoUri.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (recordingTrackId == -1L) {
|
||||
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service == null) {
|
||||
Log.d(TAG, "could not get TrackRecordingService");
|
||||
return;
|
||||
}
|
||||
|
||||
// Starts or resumes a track.
|
||||
int msg;
|
||||
if (trackId == -1L) {
|
||||
// trackId isn't initialized -> leads a new recording.
|
||||
trackId = service.startNewTrack();
|
||||
recordingTrackId = trackId;
|
||||
msg = R.string.track_detail_record_success;
|
||||
} else {
|
||||
// trackId is initialized -> resumes the track.
|
||||
recordingTrackId = trackId;
|
||||
service.resumeTrack(trackId);
|
||||
msg = R.string.track_detail_resume_success;
|
||||
}
|
||||
|
||||
// A recording track is on.
|
||||
Toast.makeText(TrackDetailActivity.this, msg, Toast.LENGTH_SHORT).show();
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackController.update(true, false);
|
||||
trackController.onResume(true, recordingTrackPaused);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,9 +160,9 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
};
|
||||
|
||||
private MenuItem insertMarkerMenuItem;
|
||||
private MenuItem insertPhotoMenuItem;
|
||||
private MenuItem markerListMenuItem;
|
||||
private MenuItem shareMenuItem;
|
||||
private MenuItem resumeMenuItem;
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
@Override
|
||||
@@ -182,16 +194,12 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
super.onCreate(savedInstanceState);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
|
||||
|
||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
||||
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
|
||||
hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
handleIntent(getIntent());
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
trackDataHub = new TrackDataHub(this);
|
||||
|
||||
FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), 1) {
|
||||
@@ -282,11 +290,16 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
trackDataHub.loadTrack(trackId);
|
||||
|
||||
// Update UI
|
||||
this.invalidateOptionsMenu();
|
||||
trackController.onResume(isRecording(), recordingTrackPaused);
|
||||
|
||||
if (trackId != -1L) {
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackController.onResume(isRecording(), recordingTrackPaused);
|
||||
} else {
|
||||
startRecording();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -307,23 +320,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
|
||||
|
||||
if (photoUri != null) {
|
||||
outState.putParcelable(PHOTO_URI_KEY, photoUri);
|
||||
}
|
||||
outState.putBoolean(HAS_PHOTO_KEY, hasPhoto);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == CAMERA_REQUEST_CODE) {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
hasPhoto = resultCode == RESULT_OK;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -343,10 +339,9 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
getMenuInflater().inflate(R.menu.track_detail, menu);
|
||||
|
||||
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
|
||||
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
|
||||
insertPhotoMenuItem.setVisible(new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null);
|
||||
shareMenuItem = menu.findItem(R.id.track_detail_share);
|
||||
markerListMenuItem = menu.findItem(R.id.track_detail_markers);
|
||||
resumeMenuItem = menu.findItem(R.id.track_detail_resume_track);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -372,9 +367,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_insert_photo:
|
||||
createWaypointWithPicture();
|
||||
return true;
|
||||
case R.id.track_detail_menu_show_on_map:
|
||||
IntentUtils.showTrackOnMap(this, new long[]{trackId});
|
||||
return true;
|
||||
@@ -391,6 +383,9 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
case R.id.track_detail_delete:
|
||||
deleteTracks(new long[]{trackId});
|
||||
return true;
|
||||
case R.id.track_detail_resume_track:
|
||||
startRecording();
|
||||
return true;
|
||||
case R.id.track_detail_settings:
|
||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
@@ -400,20 +395,6 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
createWaypointWithPicture();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrackRecordingServiceConnection getTrackRecordingServiceConnection() {
|
||||
return trackRecordingServiceConnection;
|
||||
@@ -447,9 +428,13 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
return;
|
||||
}
|
||||
trackId = waypoint.getTrackId();
|
||||
if (trackId == -1L) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (trackId == -1L) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
@@ -471,25 +456,21 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
*/
|
||||
private void updateMenuItems(boolean isPaused) {
|
||||
insertMarkerMenuItem.setVisible(isRecording() && !isPaused);
|
||||
insertPhotoMenuItem.setVisible(hasCamera && isRecording() && !isPaused);
|
||||
shareMenuItem.setVisible(!isRecording());
|
||||
markerListMenuItem.setShowAsAction(isRecording() ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
String title;
|
||||
if (isRecording()) {
|
||||
title = getString(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
resumeMenuItem.setVisible(false);
|
||||
} else {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
title = track != null ? track.getName() : "";
|
||||
// Only visible if there aren't other tracks that is recording.
|
||||
resumeMenuItem.setVisible(recordingTrackId == -1L);
|
||||
}
|
||||
setTitle(title);
|
||||
}
|
||||
|
||||
private void createWaypointWithPicture() {
|
||||
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, trackId);
|
||||
photoUri = intentAndPhotoUri.second;
|
||||
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
|
||||
}
|
||||
|
||||
public void chooseActivityType(String category) {
|
||||
ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), category);
|
||||
}
|
||||
@@ -504,4 +485,15 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA
|
||||
private boolean isRecording() {
|
||||
return trackId == recordingTrackId;
|
||||
}
|
||||
|
||||
private void startRecording() {
|
||||
trackRecordingServiceConnection.startAndBind(this);
|
||||
|
||||
/*
|
||||
* If the binding has happened, then invoke the callback to start a new recording.
|
||||
* If the binding hasn't happened, then invoking the callback will have no effect.
|
||||
* But when the binding occurs, the callback will get invoked.
|
||||
*/
|
||||
bindChangedCallback.run();
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
|
||||
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
|
||||
if (trackId == -1L) {
|
||||
Log.e(TAG, "invalid trackId");
|
||||
|
||||
@@ -160,7 +160,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
// Menu items
|
||||
private MenuItem searchMenuItem;
|
||||
private MenuItem startGpsMenuItem;
|
||||
// private MenuItem deleteAllMenuItem;
|
||||
|
||||
private final OnClickListener stopListener = new OnClickListener() {
|
||||
@Override
|
||||
@@ -172,8 +171,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
private boolean startGps = false; // true to start gps
|
||||
|
||||
private boolean startNewRecording = false; // true to start a new recording
|
||||
|
||||
// Callback when the trackRecordingServiceConnection binding changes.
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
@@ -188,7 +185,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
}
|
||||
});
|
||||
|
||||
if (!startGps && !startNewRecording) {
|
||||
if (!startGps) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,17 +194,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
Log.d(TAG, "service not available to start gps or a new recording");
|
||||
return;
|
||||
}
|
||||
if (startNewRecording) {
|
||||
startGps = false;
|
||||
|
||||
long trackId = service.startNewTrack();
|
||||
startNewRecording = false;
|
||||
Intent intent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class)
|
||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
Toast.makeText(TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
if (startGps) {
|
||||
service.startGps();
|
||||
startGps = false;
|
||||
@@ -220,18 +206,19 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
// Not recording -> Recording
|
||||
updateMenuItems(false, true);
|
||||
startRecording();
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackDetailActivity.class);
|
||||
startActivity(newIntent);
|
||||
} else if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -244,7 +231,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
trackController = new TrackController(this, trackRecordingServiceConnection, true, recordListener, stopListener);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
@@ -456,7 +443,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
/**
|
||||
* Shows start up dialogs.
|
||||
*/
|
||||
public void showStartupDialogs() {
|
||||
private void showStartupDialogs() {
|
||||
// If stats_units_key is undefined, set it
|
||||
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
|
||||
String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric);
|
||||
@@ -479,7 +466,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
* @param isRecording true if recording
|
||||
*/
|
||||
private void updateMenuItems(boolean isGpsStarted, boolean isRecording) {
|
||||
boolean hasTrack = listView != null && listView.getCount() != 0;
|
||||
if (startGpsMenuItem != null) {
|
||||
startGpsMenuItem.setVisible(!isRecording);
|
||||
if (!isRecording) {
|
||||
@@ -489,21 +475,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new recording.
|
||||
*/
|
||||
private void startRecording() {
|
||||
startNewRecording = true;
|
||||
trackRecordingServiceConnection.startAndBind(this);
|
||||
|
||||
/*
|
||||
* If the binding has happened, then invoke the callback to start a new recording.
|
||||
* If the binding hasn't happened, then invoking the callback will have no effect.
|
||||
* But when the binding occurs, the callback will get invoked.
|
||||
*/
|
||||
bindChangedCallback.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a context item selection.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package de.dennisguse.opentracks.chart;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
public class ChartPoint {
|
||||
//X-axis
|
||||
private double timeOrDistance;
|
||||
|
||||
//Y-axis
|
||||
private double elevation;
|
||||
private double speed;
|
||||
private double pace;
|
||||
private double heartRate = Double.NaN;
|
||||
private double cadence = Double.NaN;
|
||||
private double power = Double.NaN;
|
||||
|
||||
@VisibleForTesting
|
||||
ChartPoint(double elevation) {
|
||||
this.elevation = elevation;
|
||||
}
|
||||
|
||||
public ChartPoint(@NonNull TrackStatisticsUpdater trackStatisticsUpdater, SensorDataSet sensorDataSet, boolean chartByDistance, boolean metricUnits) {
|
||||
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
|
||||
|
||||
if (chartByDistance) {
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
timeOrDistance = distance;
|
||||
} else {
|
||||
timeOrDistance = trackStatistics.getTotalTime();
|
||||
}
|
||||
|
||||
elevation = trackStatisticsUpdater.getSmoothedElevation();
|
||||
if (!metricUnits) {
|
||||
elevation *= UnitConversions.M_TO_FT;
|
||||
}
|
||||
|
||||
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
pace = speed == 0 ? 0.0 : 60.0 / speed;
|
||||
if (sensorDataSet != null) {
|
||||
if (sensorDataSet.hasHeartRate()) {
|
||||
heartRate = sensorDataSet.getHeartRate();
|
||||
}
|
||||
if (sensorDataSet.hasCadence()) {
|
||||
cadence = sensorDataSet.getCadence();
|
||||
}
|
||||
if (sensorDataSet.hasPower()) {
|
||||
power = sensorDataSet.getPower();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double getTimeOrDistance() {
|
||||
return timeOrDistance;
|
||||
}
|
||||
|
||||
public double getElevation() {
|
||||
return elevation;
|
||||
}
|
||||
|
||||
public double getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public double getPace() {
|
||||
return pace;
|
||||
}
|
||||
|
||||
public boolean hasHeartRate() {
|
||||
return Double.isNaN(heartRate);
|
||||
}
|
||||
|
||||
public double getHeartRate() {
|
||||
return heartRate;
|
||||
}
|
||||
|
||||
public boolean hasCadence() {
|
||||
return Double.isNaN(cadence);
|
||||
}
|
||||
|
||||
public double getCadence() {
|
||||
return cadence;
|
||||
}
|
||||
|
||||
public boolean hasPower() {
|
||||
return Double.isNaN(power);
|
||||
}
|
||||
|
||||
public double getPower() {
|
||||
return power;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import android.graphics.Paint.Align;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Path;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||
@@ -32,7 +34,7 @@ import de.dennisguse.opentracks.stats.ExtremityMonitor;
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
class ChartValueSeries {
|
||||
abstract class ChartValueSeries {
|
||||
|
||||
private static final float STROKE_WIDTH = 2f;
|
||||
|
||||
@@ -126,26 +128,26 @@ class ChartValueSeries {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the series with a new value.
|
||||
*
|
||||
* @param value the new value
|
||||
* Updates the series with a new {@link ChartPoint}.
|
||||
*/
|
||||
void update(double value) {
|
||||
extremityMonitor.update(value);
|
||||
void update(ChartPoint chartPoint) {
|
||||
if (isChartPointValid(chartPoint)) {
|
||||
extremityMonitor.update(extractDataFromChartPoint(chartPoint));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path.
|
||||
*/
|
||||
abstract double extractDataFromChartPoint(@NonNull ChartPoint chartPoint);
|
||||
|
||||
boolean isChartPointValid(@NonNull ChartPoint chartPoint) {
|
||||
return !Double.isNaN(extractDataFromChartPoint(chartPoint));
|
||||
}
|
||||
|
||||
protected abstract boolean drawIfChartPointHasNoData();
|
||||
|
||||
Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the path on canvas.
|
||||
*
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
void drawPath(Canvas canvas) {
|
||||
canvas.drawPath(path, fillPaint);
|
||||
canvas.drawPath(path, strokePaint);
|
||||
|
||||
@@ -33,6 +33,7 @@ import android.view.ScaleGestureDetector;
|
||||
import android.view.View;
|
||||
import android.widget.Scroller;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.GestureDetectorCompat;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
@@ -62,14 +63,6 @@ public class ChartView extends View {
|
||||
|
||||
static final int Y_AXIS_INTERVALS = 5;
|
||||
|
||||
public static final int NUM_SERIES = 6;
|
||||
public static final int ELEVATION_SERIES = 0;
|
||||
public static final int SPEED_SERIES = 1;
|
||||
public static final int PACE_SERIES = 2;
|
||||
public static final int HEART_RATE_SERIES = 3;
|
||||
public static final int CADENCE_SERIES = 4;
|
||||
public static final int POWER_SERIES = 5;
|
||||
|
||||
private static final int TARGET_X_AXIS_INTERVALS = 4;
|
||||
|
||||
private static final int MIN_ZOOM_LEVEL = 1;
|
||||
@@ -89,8 +82,11 @@ public class ChartView extends View {
|
||||
X_FRACTION_FORMAT.setMinimumFractionDigits(1);
|
||||
}
|
||||
|
||||
private final ChartValueSeries[] series = new ChartValueSeries[NUM_SERIES];
|
||||
private final List<double[]> chartData = new ArrayList<>();
|
||||
private final List<ChartValueSeries> seriesList = new ArrayList<>();
|
||||
private final ChartValueSeries speedSeries;
|
||||
private final ChartValueSeries paceSeries;
|
||||
|
||||
private final List<ChartPoint> chartPoints = new ArrayList<>();
|
||||
private final List<Waypoint> waypoints = new ArrayList<>();
|
||||
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
|
||||
private final Paint axisPaint;
|
||||
@@ -192,58 +188,124 @@ public class ChartView extends View {
|
||||
}
|
||||
});
|
||||
|
||||
public ChartView(Context context, boolean chartByDistance) {
|
||||
public ChartView(Context context, final boolean chartByDistance) {
|
||||
super(context);
|
||||
this.chartByDistance = chartByDistance;
|
||||
|
||||
series[ELEVATION_SERIES] = new ChartValueSeries(context,
|
||||
seriesList.add(new ChartValueSeries(context,
|
||||
Integer.MIN_VALUE,
|
||||
Integer.MAX_VALUE,
|
||||
new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000},
|
||||
R.string.description_elevation_metric,
|
||||
R.string.description_elevation_imperial,
|
||||
R.color.chart_elevation_fill,
|
||||
R.color.chart_elevation_border);
|
||||
series[SPEED_SERIES] = new ChartValueSeries(context,
|
||||
R.color.chart_elevation_border) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getElevation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawIfChartPointHasNoData() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
speedSeries = new ChartValueSeries(context,
|
||||
0,
|
||||
Integer.MAX_VALUE,
|
||||
new int[]{1, 5, 10, 20, 50, 100},
|
||||
R.string.description_speed_metric,
|
||||
R.string.description_speed_imperial,
|
||||
R.color.chart_speed_fill,
|
||||
R.color.chart_speed_border);
|
||||
series[PACE_SERIES] = new ChartValueSeries(context,
|
||||
R.color.chart_speed_border) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getSpeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawIfChartPointHasNoData() {
|
||||
return reportSpeed;
|
||||
}
|
||||
};
|
||||
seriesList.add(speedSeries);
|
||||
|
||||
paceSeries = new ChartValueSeries(context,
|
||||
0,
|
||||
Integer.MAX_VALUE,
|
||||
new int[]{1, 2, 5, 10, 15, 20, 30, 60, 120},
|
||||
R.string.description_pace_metric,
|
||||
R.string.description_pace_imperial,
|
||||
R.color.chart_pace_fill,
|
||||
R.color.chart_pace_border);
|
||||
series[HEART_RATE_SERIES] = new ChartValueSeries(context,
|
||||
R.color.chart_pace_border) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getPace();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawIfChartPointHasNoData() {
|
||||
return !reportSpeed;
|
||||
}
|
||||
};
|
||||
seriesList.add(paceSeries);
|
||||
|
||||
seriesList.add(new ChartValueSeries(context,
|
||||
0,
|
||||
Integer.MAX_VALUE,
|
||||
new int[]{25, 50},
|
||||
R.string.description_sensor_heart_rate,
|
||||
R.string.description_sensor_heart_rate,
|
||||
R.color.chart_heart_rate_fill,
|
||||
R.color.chart_heart_rate_border);
|
||||
series[CADENCE_SERIES] = new ChartValueSeries(context,
|
||||
R.color.chart_heart_rate_border) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getHeartRate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawIfChartPointHasNoData() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
seriesList.add(new ChartValueSeries(context,
|
||||
0,
|
||||
Integer.MAX_VALUE,
|
||||
new int[]{5, 10, 25, 50},
|
||||
R.string.description_sensor_cadence,
|
||||
R.string.description_sensor_cadence,
|
||||
R.color.chart_cadence_fill,
|
||||
R.color.chart_cadence_border);
|
||||
series[POWER_SERIES] = new ChartValueSeries(context,
|
||||
R.color.chart_cadence_border) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getCadence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawIfChartPointHasNoData() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
seriesList.add(new ChartValueSeries(context,
|
||||
0,
|
||||
1000,
|
||||
new int[]{5, 50, 100, 200},
|
||||
R.string.description_sensor_power,
|
||||
R.string.description_sensor_power,
|
||||
R.color.chart_power_fill,
|
||||
R.color.chart_power_border);
|
||||
R.color.chart_power_border) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getPower();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawIfChartPointHasNoData() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
float scale = context.getResources().getDisplayMetrics().density;
|
||||
|
||||
@@ -279,6 +341,10 @@ public class ChartView extends View {
|
||||
setFocusable(true);
|
||||
setClickable(true);
|
||||
updateDimensions();
|
||||
|
||||
// either speedSeries or paceSeries should be enabled.
|
||||
speedSeries.setEnabled(reportSpeed);
|
||||
paceSeries.setEnabled(!reportSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -286,13 +352,8 @@ public class ChartView extends View {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the enabled value for a chart value series.
|
||||
*
|
||||
* @param index the chart value series index
|
||||
*/
|
||||
public void setChartValueSeriesEnabled(int index, boolean enabled) {
|
||||
series[index].setEnabled(enabled);
|
||||
public boolean getMetricUnits() {
|
||||
return metricUnits;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,38 +365,48 @@ public class ChartView extends View {
|
||||
metricUnits = value;
|
||||
}
|
||||
|
||||
public boolean getReportSpeed() {
|
||||
return reportSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets report speed.
|
||||
*
|
||||
* @param value true to report speed
|
||||
* @param value report speed (true) or pace (false)
|
||||
*/
|
||||
public void setReportSpeed(boolean value) {
|
||||
reportSpeed = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets show pointer.
|
||||
*
|
||||
* @param value true to show pointer
|
||||
*/
|
||||
public boolean applyReportSpeed() {
|
||||
if (reportSpeed) {
|
||||
if (!speedSeries.isEnabled()) {
|
||||
speedSeries.setEnabled(true);
|
||||
paceSeries.setEnabled(false);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!paceSeries.isEnabled()) {
|
||||
speedSeries.setEnabled(false);
|
||||
paceSeries.setEnabled(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setShowPointer(boolean value) {
|
||||
showPointer = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data points.
|
||||
*
|
||||
* @param dataPoints an array of data points to be added
|
||||
*/
|
||||
public void addDataPoints(List<double[]> dataPoints) {
|
||||
synchronized (chartData) {
|
||||
chartData.addAll(dataPoints);
|
||||
for (double[] dataPoint : dataPoints) {
|
||||
xExtremityMonitor.update(dataPoint[0]);
|
||||
for (int j = 0; j < series.length; j++) {
|
||||
if (!Double.isNaN(dataPoint[j + 1])) {
|
||||
series[j].update(dataPoint[j + 1]);
|
||||
}
|
||||
public void addChartPoints(List<ChartPoint> dataPoints) {
|
||||
synchronized (chartPoints) {
|
||||
chartPoints.addAll(dataPoints);
|
||||
for (ChartPoint dataPoint : dataPoints) {
|
||||
xExtremityMonitor.update(dataPoint.getTimeOrDistance());
|
||||
for (ChartValueSeries i : seriesList) {
|
||||
i.update(dataPoint);
|
||||
}
|
||||
}
|
||||
updateDimensions();
|
||||
@@ -347,8 +418,8 @@ public class ChartView extends View {
|
||||
* Clears all data.
|
||||
*/
|
||||
public void reset() {
|
||||
synchronized (chartData) {
|
||||
chartData.clear();
|
||||
synchronized (chartPoints) {
|
||||
chartPoints.clear();
|
||||
xExtremityMonitor.reset();
|
||||
zoomLevel = 1;
|
||||
updateDimensions();
|
||||
@@ -356,50 +427,34 @@ public class ChartView extends View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets scroll. To be called on the UI thread.
|
||||
* Resets scroll.
|
||||
* To be called on the UI thread.
|
||||
*/
|
||||
public void resetScroll() {
|
||||
scrollTo(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a waypoint.
|
||||
*
|
||||
* @param waypoint the waypoint
|
||||
*/
|
||||
public void addWaypoint(Waypoint waypoint) {
|
||||
synchronized (waypoints) {
|
||||
waypoints.add(waypoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the waypoints.
|
||||
*/
|
||||
public void clearWaypoints() {
|
||||
synchronized (waypoints) {
|
||||
waypoints.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if can zoom in.
|
||||
*/
|
||||
public boolean canZoomIn() {
|
||||
private boolean canZoomIn() {
|
||||
return zoomLevel < MAX_ZOOM_LEVEL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if can zoom out.
|
||||
*/
|
||||
public boolean canZoomOut() {
|
||||
private boolean canZoomOut() {
|
||||
return zoomLevel > MIN_ZOOM_LEVEL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms in one level.
|
||||
*/
|
||||
public void zoomIn() {
|
||||
private void zoomIn() {
|
||||
if (canZoomIn()) {
|
||||
zoomLevel++;
|
||||
updatePaths();
|
||||
@@ -407,10 +462,7 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zooms out one level.
|
||||
*/
|
||||
public void zoomOut() {
|
||||
private void zoomOut() {
|
||||
if (canZoomOut()) {
|
||||
zoomLevel--;
|
||||
scroller.abortAnimation();
|
||||
@@ -430,7 +482,7 @@ public class ChartView extends View {
|
||||
*
|
||||
* @param velocityX velocity of fling in pixels per second
|
||||
*/
|
||||
public void fling(int velocityX) {
|
||||
private void fling(int velocityX) {
|
||||
int maxWidth = effectiveWidth * (zoomLevel - 1);
|
||||
scroller.fling(getScrollX(), 0, velocityX, 0, 0, maxWidth, 0, 0);
|
||||
invalidate();
|
||||
@@ -441,7 +493,7 @@ public class ChartView extends View {
|
||||
*
|
||||
* @param deltaX the number of pixels to scroll
|
||||
*/
|
||||
public void scrollBy(int deltaX) {
|
||||
private void scrollBy(int deltaX) {
|
||||
int scrollX = getScrollX() + deltaX;
|
||||
if (scrollX < 0) {
|
||||
scrollX = 0;
|
||||
@@ -485,8 +537,7 @@ public class ChartView extends View {
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
synchronized (chartData) {
|
||||
|
||||
synchronized (chartPoints) {
|
||||
canvas.save();
|
||||
|
||||
canvas.drawColor(Color.WHITE);
|
||||
@@ -529,7 +580,7 @@ public class ChartView extends View {
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
private void drawDataSeries(Canvas canvas) {
|
||||
for (ChartValueSeries chartValueSeries : series) {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
|
||||
chartValueSeries.drawPath(canvas);
|
||||
}
|
||||
@@ -543,11 +594,7 @@ public class ChartView extends View {
|
||||
*/
|
||||
private void drawWaypoints(Canvas canvas) {
|
||||
synchronized (waypoints) {
|
||||
for (int i = 0; i < waypoints.size(); i++) {
|
||||
final Waypoint waypoint = waypoints.get(i);
|
||||
if (waypoint.getLocation() == null) {
|
||||
continue;
|
||||
}
|
||||
for (Waypoint waypoint : waypoints) {
|
||||
double xValue = getWaypointXValue(waypoint);
|
||||
if (xValue > maxX) {
|
||||
continue;
|
||||
@@ -595,9 +642,8 @@ public class ChartView extends View {
|
||||
int lines = titleDimensions[0];
|
||||
int lineHeight = titleDimensions[1];
|
||||
int count = 0;
|
||||
for (int i = 0; i < series.length; i++) {
|
||||
ChartValueSeries chartValueSeries = series[i];
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
|
||||
count++;
|
||||
String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
|
||||
Paint paint = chartValueSeries.getTitlePaint();
|
||||
@@ -615,9 +661,8 @@ public class ChartView extends View {
|
||||
private int[] getTitleDimensions() {
|
||||
int lines = 0;
|
||||
int lineHeight = 0;
|
||||
for (int i = 0; i < series.length; i++) {
|
||||
ChartValueSeries chartValueSeries = series[i];
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
|
||||
lines++;
|
||||
String title = getContext().getString(chartValueSeries.getTitleId(metricUnits));
|
||||
Rect rect = getRect(chartValueSeries.getTitlePaint(), title);
|
||||
@@ -651,9 +696,6 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the x axis label.
|
||||
*/
|
||||
private String getXAxisLabel() {
|
||||
Context context = getContext();
|
||||
if (chartByDistance) {
|
||||
@@ -677,9 +719,6 @@ public class ChartView extends View {
|
||||
canvas.drawText(marker, getX(value), topBorder + effectiveHeight + spacing + rect.height(), xAxisMarkerPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the x axis interval.
|
||||
*/
|
||||
private double getXAxisInterval() {
|
||||
double interval = maxX / zoomLevel / TARGET_X_AXIS_INTERVALS;
|
||||
if (interval < 1) {
|
||||
@@ -694,16 +733,13 @@ public class ChartView extends View {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the x axis marker positions.
|
||||
*/
|
||||
private List<Double> getXAxisMarkerPositions(double interval) {
|
||||
List<Double> markers = new ArrayList<>();
|
||||
markers.add(0d);
|
||||
for (int i = 1; i * interval < maxX; i++) {
|
||||
markers.add(i * interval);
|
||||
}
|
||||
// At least 2 markers
|
||||
|
||||
if (markers.size() < 2) {
|
||||
markers.add(maxX);
|
||||
}
|
||||
@@ -720,11 +756,12 @@ public class ChartView extends View {
|
||||
int y = topBorder;
|
||||
canvas.drawLine(x, y, x, y + effectiveHeight, axisPaint);
|
||||
|
||||
//TODO
|
||||
int markerXPosition = x - spacer;
|
||||
for (int i = 0; i < series.length; i++) {
|
||||
int index = series.length - 1 - i;
|
||||
ChartValueSeries chartValueSeries = series[index];
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(index)) {
|
||||
for (int i = 0; i < seriesList.size(); i++) {
|
||||
int index = seriesList.size() - 1 - i;
|
||||
ChartValueSeries chartValueSeries = seriesList.get(index);
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
|
||||
markerXPosition -= drawYAxisMarkers(chartValueSeries, canvas, markerXPosition) + spacer;
|
||||
}
|
||||
}
|
||||
@@ -772,29 +809,29 @@ public class ChartView extends View {
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
private void drawPointer(Canvas canvas) {
|
||||
int index = -1;
|
||||
for (int i = 0; i < series.length; i++) {
|
||||
ChartValueSeries chartValueSeries = series[i];
|
||||
ChartValueSeries firstChartValueSeries = null;
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData()) {
|
||||
index = i;
|
||||
firstChartValueSeries = chartValueSeries;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index != -1 && chartData.size() > 0) {
|
||||
if (firstChartValueSeries != null && chartPoints.size() > 0) {
|
||||
int dx = getX(maxX) - pointer.getIntrinsicWidth() / 2;
|
||||
int dy = getY(series[index], chartData.get(chartData.size() - 1)[index + 1])
|
||||
- pointer.getIntrinsicHeight();
|
||||
double value = firstChartValueSeries.extractDataFromChartPoint(chartPoints.get(chartPoints.size() - 1));
|
||||
int dy = getY(firstChartValueSeries, value) - pointer.getIntrinsicHeight();
|
||||
canvas.translate(dx, dy);
|
||||
pointer.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates paths. The path needs to be updated any time after the data or the dimensions change.
|
||||
* Updates paths.
|
||||
* The path needs to be updated any time after the data or the dimensions change.
|
||||
*/
|
||||
private void updatePaths() {
|
||||
synchronized (chartData) {
|
||||
for (ChartValueSeries chartValueSeries : series) {
|
||||
synchronized (chartPoints) {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
chartValueSeries.getPath().reset();
|
||||
}
|
||||
drawPaths();
|
||||
@@ -802,24 +839,23 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws all paths.
|
||||
*/
|
||||
private void drawPaths() {
|
||||
boolean[] hasMoved = new boolean[series.length];
|
||||
boolean[] hasMoved = new boolean[seriesList.size()];
|
||||
|
||||
for (double[] dataPoint : chartData) {
|
||||
for (int j = 0; j < series.length; j++) {
|
||||
double value = dataPoint[j + 1];
|
||||
if (Double.isNaN(value)) {
|
||||
for (ChartPoint dataPoint : chartPoints) {
|
||||
for (int i = 0; i < seriesList.size(); i++) {
|
||||
ChartValueSeries chartValueSeries = seriesList.get(i);
|
||||
|
||||
if (chartValueSeries.isChartPointValid(dataPoint)) {
|
||||
continue;
|
||||
}
|
||||
ChartValueSeries chartValueSeries = series[j];
|
||||
|
||||
double value = chartValueSeries.extractDataFromChartPoint(dataPoint);
|
||||
Path path = chartValueSeries.getPath();
|
||||
int x = getX(dataPoint[0]);
|
||||
int x = getX(dataPoint.getTimeOrDistance());
|
||||
int y = getY(chartValueSeries, value);
|
||||
if (!hasMoved[j]) {
|
||||
hasMoved[j] = true;
|
||||
if (!hasMoved[i]) {
|
||||
hasMoved[i] = true;
|
||||
path.moveTo(x, y);
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
@@ -828,37 +864,32 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all paths.
|
||||
*/
|
||||
private void closePaths() {
|
||||
for (int i = 0; i < series.length; i++) {
|
||||
int first = getFirstPopulatedChartDataIndex(i);
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
int first = getFirstPopulatedChartDataIndex(chartValueSeries);
|
||||
|
||||
if (first != -1) {
|
||||
int xCorner = getX(chartData.get(first)[0]);
|
||||
int xCorner = getX(chartPoints.get(first).getTimeOrDistance());
|
||||
int yCorner = topBorder + effectiveHeight;
|
||||
ChartValueSeries chartValueSeries = series[i];
|
||||
Path path = chartValueSeries.getPath();
|
||||
// Bottom right corner
|
||||
path.lineTo(getX(chartData.get(chartData.size() - 1)[0]), yCorner);
|
||||
path.lineTo(getX(chartPoints.get(chartPoints.size() - 1).getTimeOrDistance()), yCorner);
|
||||
// Bottom left corner
|
||||
path.lineTo(xCorner, yCorner);
|
||||
// Top right corner
|
||||
path.lineTo(xCorner, getY(chartValueSeries, chartData.get(first)[i + 1]));
|
||||
double value = chartValueSeries.extractDataFromChartPoint(chartPoints.get(first));
|
||||
path.lineTo(xCorner, getY(chartValueSeries, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the first data point containing data for a series.
|
||||
* Returns -1 if no data point contains data for the series.
|
||||
*
|
||||
* @param seriesIndex the series's index
|
||||
* @return -1 if no data point contains data for the series.
|
||||
*/
|
||||
private int getFirstPopulatedChartDataIndex(int seriesIndex) {
|
||||
for (int i = 0; i < chartData.size(); i++) {
|
||||
if (!Double.isNaN(chartData.get(i)[seriesIndex + 1])) {
|
||||
private int getFirstPopulatedChartDataIndex(ChartValueSeries chartValueSeries) {
|
||||
for (int i = 0; i < chartPoints.size(); i++) {
|
||||
if (chartValueSeries.isChartPointValid(chartPoints.get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -870,7 +901,7 @@ public class ChartView extends View {
|
||||
*/
|
||||
private void updateDimensions() {
|
||||
maxX = xExtremityMonitor.hasData() ? xExtremityMonitor.getMax() : 1.0;
|
||||
for (ChartValueSeries chartValueSeries : series) {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
chartValueSeries.updateDimension();
|
||||
}
|
||||
float density = getContext().getResources().getDisplayMetrics().density;
|
||||
@@ -878,9 +909,8 @@ public class ChartView extends View {
|
||||
yAxisOffset = (int) (density * Y_AXIS_OFFSET);
|
||||
|
||||
int markerLength = 0;
|
||||
for (int i = 0; i < series.length; i++) {
|
||||
ChartValueSeries chartValueSeries = series[i];
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(i)) {
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
|
||||
Rect rect = getRect(chartValueSeries.getMarkerPaint(), chartValueSeries.getLargestMarker());
|
||||
markerLength += rect.width() + spacer;
|
||||
}
|
||||
@@ -973,22 +1003,12 @@ public class ChartView extends View {
|
||||
|
||||
/**
|
||||
* Returns true if the index is allowed when the chartData is empty.
|
||||
*
|
||||
* @param index the index
|
||||
*/
|
||||
private boolean allowIfEmpty(int index) {
|
||||
if (!chartData.isEmpty()) {
|
||||
private boolean allowIfEmpty(ChartValueSeries chartValueSeries) {
|
||||
if (!chartPoints.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
switch (index) {
|
||||
case ELEVATION_SERIES:
|
||||
return true;
|
||||
case SPEED_SERIES:
|
||||
return reportSpeed;
|
||||
case PACE_SERIES:
|
||||
return !reportSpeed;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return chartValueSeries.drawIfChartPointHasNoData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Data source on the phone.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class DataSource {
|
||||
|
||||
private final ContentResolver contentResolver;
|
||||
|
||||
DataSource(Context context) {
|
||||
contentResolver = context.getContentResolver();
|
||||
}
|
||||
|
||||
void registerContentObserver(Uri uri, ContentObserver observer) {
|
||||
contentResolver.registerContentObserver(uri, false, observer);
|
||||
}
|
||||
|
||||
void unregisterContentObserver(ContentObserver observer) {
|
||||
contentResolver.unregisterContentObserver(observer);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
|
||||
/**
|
||||
* Listener to be invoked when {@link DataSource} changes.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public interface DataSourceListener {
|
||||
|
||||
/**
|
||||
* Notifies when the tracks table is updated.
|
||||
*/
|
||||
void notifyTracksTableUpdated();
|
||||
|
||||
/**
|
||||
* Notifies when the waypoints table is updated.
|
||||
*/
|
||||
void notifyWaypointsTableUpdated();
|
||||
|
||||
/**
|
||||
* Notifies when the track points table is updated.
|
||||
*/
|
||||
void notifyTrackPointsTableUpdated();
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.database.ContentObserver;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
|
||||
/**
|
||||
* Creates observers/listeners and manages their registration with {@link DataSource}.
|
||||
* The observers/listeners calls {@link DataSourceListener} when data changes.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
class DataSourceManager {
|
||||
|
||||
private static final String TAG = DataSourceManager.class.getSimpleName();
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final DataSourceListener dataSourceListener;
|
||||
|
||||
// Registered listeners
|
||||
private final Set<TrackDataType> registeredListeners = EnumSet.noneOf(TrackDataType.class);
|
||||
private final Handler handler;
|
||||
private final TracksTableObserver tracksTableObserver;
|
||||
private final WaypointsTableObserver waypointsTableObserver;
|
||||
private final TrackPointsTableObserver trackPointsTableObserver;
|
||||
|
||||
DataSourceManager(DataSource dataSource, DataSourceListener dataSourceListener) {
|
||||
this.dataSource = dataSource;
|
||||
this.dataSourceListener = dataSourceListener;
|
||||
|
||||
handler = new Handler();
|
||||
tracksTableObserver = new TracksTableObserver();
|
||||
waypointsTableObserver = new WaypointsTableObserver();
|
||||
trackPointsTableObserver = new TrackPointsTableObserver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates listeners with data source.
|
||||
*
|
||||
* @param listeners the listeners
|
||||
*/
|
||||
void updateListeners(EnumSet<TrackDataType> listeners) {
|
||||
EnumSet<TrackDataType> neededListeners = EnumSet.copyOf(listeners);
|
||||
|
||||
// Map SAMPLED_OUT_POINT_UPDATES to POINT_UPDATES since they correspond to the same internal listener
|
||||
if (neededListeners.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE)) {
|
||||
neededListeners.remove(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE);
|
||||
neededListeners.add(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE);
|
||||
}
|
||||
|
||||
Log.d(TAG, "Updating listeners " + neededListeners);
|
||||
|
||||
// Unnecessary = registered - needed
|
||||
Set<TrackDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
|
||||
unnecessaryListeners.removeAll(neededListeners);
|
||||
|
||||
// Missing = needed - registered
|
||||
Set<TrackDataType> missingListeners = EnumSet.copyOf(neededListeners);
|
||||
missingListeners.removeAll(registeredListeners);
|
||||
|
||||
// Remove unnecessary listeners
|
||||
for (TrackDataType trackDataType : unnecessaryListeners) {
|
||||
unregisterListener(trackDataType);
|
||||
}
|
||||
|
||||
// Add missing listeners
|
||||
for (TrackDataType trackDataType : missingListeners) {
|
||||
registerListener(trackDataType);
|
||||
}
|
||||
|
||||
// Update registered listeners
|
||||
registeredListeners.clear();
|
||||
registeredListeners.addAll(neededListeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener with data source.
|
||||
*
|
||||
* @param trackDataType the listener data type
|
||||
*/
|
||||
private void registerListener(TrackDataType trackDataType) {
|
||||
switch (trackDataType) {
|
||||
case TRACKS_TABLE:
|
||||
dataSource.registerContentObserver(TracksColumns.CONTENT_URI, tracksTableObserver);
|
||||
break;
|
||||
case WAYPOINTS_TABLE:
|
||||
dataSource.registerContentObserver(WaypointsColumns.CONTENT_URI, waypointsTableObserver);
|
||||
break;
|
||||
case SAMPLED_IN_TRACK_POINTS_TABLE:
|
||||
dataSource.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, trackPointsTableObserver);
|
||||
break;
|
||||
case SAMPLED_OUT_TRACK_POINTS_TABLE:
|
||||
// Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a listener with data source.
|
||||
*
|
||||
* @param trackDataType listener data type
|
||||
*/
|
||||
private void unregisterListener(TrackDataType trackDataType) {
|
||||
switch (trackDataType) {
|
||||
case TRACKS_TABLE:
|
||||
dataSource.unregisterContentObserver(tracksTableObserver);
|
||||
break;
|
||||
case WAYPOINTS_TABLE:
|
||||
dataSource.unregisterContentObserver(waypointsTableObserver);
|
||||
break;
|
||||
case SAMPLED_IN_TRACK_POINTS_TABLE:
|
||||
dataSource.unregisterContentObserver(trackPointsTableObserver);
|
||||
break;
|
||||
case SAMPLED_OUT_TRACK_POINTS_TABLE:
|
||||
// Do nothing. SAMPLED_OUT_POINT_UPDATES is mapped to POINT_UPDATES.
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all listeners with data source.
|
||||
*/
|
||||
void unregisterAllListeners() {
|
||||
for (TrackDataType trackDataType : TrackDataType.values()) {
|
||||
unregisterListener(trackDataType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observer when the tracks table is updated.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private class TracksTableObserver extends ContentObserver {
|
||||
|
||||
TracksTableObserver() {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
dataSourceListener.notifyTracksTableUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observer when the waypoints table is updated.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private class WaypointsTableObserver extends ContentObserver {
|
||||
|
||||
WaypointsTableObserver() {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
dataSourceListener.notifyWaypointsTableUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observer when the track points table is updated.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private class TrackPointsTableObserver extends ContentObserver {
|
||||
|
||||
TrackPointsTableObserver() {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
dataSourceListener.notifyTrackPointsTableUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -67,7 +67,7 @@ public class DescriptionGenerator {
|
||||
writeString(track.getName(), builder, R.string.generic_name_line, lineBreak);
|
||||
writeString(track.getCategory(), builder, R.string.description_activity_type, lineBreak);
|
||||
writeString(track.getDescription(), builder, R.string.generic_description_line, lineBreak);
|
||||
builder.append(generateTripStatisticsDescription(track.getTripStatistics(), html));
|
||||
builder.append(generateTrackStatisticsDescription(track.getTrackStatistics(), html));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
@@ -89,12 +89,12 @@ public class DescriptionGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a description for a {@link TripStatistics}.
|
||||
* Generates a description for a {@link TrackStatistics}.
|
||||
*
|
||||
* @param stats the trip statistics
|
||||
* @param stats the track statistics
|
||||
* @param html true to use "<br>" for line break instead of "\n"
|
||||
*/
|
||||
private String generateTripStatisticsDescription(TripStatistics stats, boolean html) {
|
||||
private String generateTrackStatisticsDescription(TrackStatistics stats, boolean html) {
|
||||
String lineBreak = html ? HTML_LINE_BREAK : TEXT_LINE_BREAK;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
@@ -134,14 +134,8 @@ public class DescriptionGenerator {
|
||||
// Elevation gain
|
||||
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
||||
|
||||
// Max grade
|
||||
writeGrade(stats.getMaxGrade(), builder, R.string.description_max_grade, lineBreak);
|
||||
|
||||
// Min grade
|
||||
writeGrade(stats.getMinGrade(), builder, R.string.description_min_grade, lineBreak);
|
||||
|
||||
// Recorded time
|
||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime())));
|
||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime_ms())));
|
||||
builder.append(lineBreak);
|
||||
|
||||
return builder.toString();
|
||||
@@ -228,19 +222,4 @@ public class DescriptionGenerator {
|
||||
builder.append(context.getString(resId, elevationInM, elevationInFt));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes grade.
|
||||
*
|
||||
* @param grade grade in fraction
|
||||
* @param builder StringBuilder to append grade
|
||||
* @param resId resource id grade string
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeGrade(double grade, StringBuilder builder, int resId, String lineBreak) {
|
||||
long gradeInPercent = Double.isNaN(grade) || Double.isInfinite(grade) ? 0L : Math.round(grade * 100);
|
||||
builder.append(context.getString(resId, gradeInPercent));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -247,9 +247,9 @@ public class SearchEngine {
|
||||
|
||||
score *= getTitleBoost(query, track.getName(), track.getDescription(), track.getCategory());
|
||||
|
||||
TripStatistics statistics = track.getTripStatistics();
|
||||
TrackStatistics statistics = track.getTrackStatistics();
|
||||
|
||||
long meanTimestamp = (statistics.getStartTime() + statistics.getStopTime()) / 2L;
|
||||
long meanTimestamp = (statistics.getStartTime_ms() + statistics.getStopTime_ms()) / 2L;
|
||||
score *= getTimeBoost(query, meanTimestamp);
|
||||
|
||||
// Score the currently-selected track lower (user is already there, wouldn't be searching for it).
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
@@ -27,24 +28,29 @@ import android.util.Log;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointFactory;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Track data hub. Receives data from {@link DataSource} and distributes it to {@link TrackDataListener} after some processing.
|
||||
* Track data hub.
|
||||
* Receives data from {@link de.dennisguse.opentracks.content.provider.CustomContentProvider} and distributes it to {@link TrackDataListener} after some processing.
|
||||
*
|
||||
* {@link TrackPoint}s are filtered/downsampled with a dynamic sampling frequency.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
/**
|
||||
* Target number of track points displayed by the map overlay.
|
||||
@@ -70,8 +76,6 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
private boolean started;
|
||||
private HandlerThread handlerThread;
|
||||
private Handler handler;
|
||||
private DataSource dataSource;
|
||||
private DataSourceManager dataSourceManager;
|
||||
|
||||
// Preference values
|
||||
private long selectedTrackId;
|
||||
@@ -80,8 +84,13 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
|
||||
// Track points sampling state
|
||||
private int numLoadedPoints;
|
||||
private long firstSeenLocationId;
|
||||
private long lastSeenLocationId;
|
||||
private long firstSeenTrackPointId;
|
||||
private long lastSeenTrackPointId;
|
||||
|
||||
// Registered listeners
|
||||
private ContentObserver tracksTableObserver;
|
||||
private ContentObserver waypointsTableObserver;
|
||||
private ContentObserver trackPointsTableObserver;
|
||||
|
||||
public TrackDataHub(Context context) {
|
||||
this(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
|
||||
@@ -102,19 +111,43 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
return;
|
||||
}
|
||||
started = true;
|
||||
handlerThread = new HandlerThread("TrackDataHubHandlerThread");
|
||||
handlerThread = new HandlerThread(TAG);
|
||||
handlerThread.start();
|
||||
handler = new Handler(handlerThread.getLooper());
|
||||
dataSource = newDataSource();
|
||||
dataSourceManager = new DataSourceManager(dataSource, this);
|
||||
|
||||
//register listeners
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
tracksTableObserver = new ContentObserver(handler) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
notifyTracksTableUpdate(trackDataManager.getListenerTracks());
|
||||
}
|
||||
};
|
||||
contentResolver.registerContentObserver(TracksColumns.CONTENT_URI, false, tracksTableObserver);
|
||||
|
||||
waypointsTableObserver = new ContentObserver(handler) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
notifyWaypointsTableUpdate(trackDataManager.getListenerWaypoints());
|
||||
}
|
||||
};
|
||||
contentResolver.registerContentObserver(WaypointsColumns.CONTENT_URI, false, waypointsTableObserver);
|
||||
|
||||
trackPointsTableObserver = new ContentObserver(handler) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
notifyTrackPointsTableUpdate(true, trackDataManager.getListenerTrackPoints_SampledIn(), trackDataManager.getListenerTrackPoints_SampledOut());
|
||||
}
|
||||
};
|
||||
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
|
||||
|
||||
|
||||
PreferencesUtils.register(context, this);
|
||||
onSharedPreferenceChanged(null, null);
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (dataSourceManager != null) {
|
||||
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
}
|
||||
}
|
||||
@@ -131,14 +164,17 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
|
||||
started = false;
|
||||
|
||||
dataSourceManager.unregisterAllListeners();
|
||||
//Unregister listeners
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
contentResolver.unregisterContentObserver(tracksTableObserver);
|
||||
contentResolver.unregisterContentObserver(waypointsTableObserver);
|
||||
contentResolver.unregisterContentObserver(trackPointsTableObserver);
|
||||
|
||||
if (handlerThread != null) {
|
||||
handlerThread.getLooper().quit();
|
||||
handlerThread = null;
|
||||
}
|
||||
handler = null;
|
||||
dataSource = null;
|
||||
dataSourceManager = null;
|
||||
}
|
||||
|
||||
public void loadTrack(final long trackId) {
|
||||
@@ -159,15 +195,13 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
* Registers a {@link TrackDataListener}.
|
||||
*
|
||||
* @param trackDataListener the track data listener
|
||||
* @param trackDataTypes the track data types
|
||||
*/
|
||||
public void registerTrackDataListener(final TrackDataListener trackDataListener, final EnumSet<TrackDataType> trackDataTypes) {
|
||||
public void registerTrackDataListener(final TrackDataListener trackDataListener, final boolean tracksTable, final boolean waypointsTable, final boolean trackPointsTable_SampleIn, final boolean trackPointsTable_SampleOut) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackDataManager.registerListener(trackDataListener, trackDataTypes);
|
||||
if (dataSourceManager != null) {
|
||||
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
|
||||
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
||||
if (started) {
|
||||
loadDataForListener(trackDataListener);
|
||||
}
|
||||
}
|
||||
@@ -183,22 +217,7 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
trackDataManager.unregisterListener(trackDataListener);
|
||||
if (dataSourceManager != null) {
|
||||
dataSourceManager.updateListeners(trackDataManager.getRegisteredTrackDataTypes());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads data for a {@link TrackDataListener}.
|
||||
*/
|
||||
public void reloadDataForListener(final TrackDataListener trackDataListener) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadDataForListener(trackDataListener);
|
||||
trackDataManager.unregisterTrackDataListener(trackDataListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -217,37 +236,6 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
return selectedTrackId == recordingTrackId && recordingTrackPaused;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTracksTableUpdated() {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
notifyTracksTableUpdate(trackDataManager.getListeners(TrackDataType.TRACKS_TABLE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyWaypointsTableUpdated() {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
notifyWaypointsTableUpdate(trackDataManager.getListeners(TrackDataType.WAYPOINTS_TABLE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTrackPointsTableUpdated() {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
notifyTrackPointsTableUpdate(true, trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
|
||||
trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
||||
runInHandlerThread(new Runnable() {
|
||||
@@ -268,19 +256,17 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
*/
|
||||
private void loadDataForAll() {
|
||||
resetSamplingState();
|
||||
if (trackDataManager.getNumberOfListeners() == 0) {
|
||||
if (!trackDataManager.hasListeners()) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyTracksTableUpdate(trackDataManager.getListeners(TrackDataType.TRACKS_TABLE));
|
||||
notifyTracksTableUpdate(trackDataManager.getListenerTracks());
|
||||
|
||||
for (TrackDataListener listener : trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE)) {
|
||||
for (TrackDataListener listener : trackDataManager.getListenerTrackPoints_SampledIn()) {
|
||||
listener.clearTrackPoints();
|
||||
}
|
||||
notifyTrackPointsTableUpdate(true,
|
||||
trackDataManager.getListeners(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE),
|
||||
trackDataManager.getListeners(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
|
||||
notifyWaypointsTableUpdate(trackDataManager.getListeners(TrackDataType.WAYPOINTS_TABLE));
|
||||
notifyTrackPointsTableUpdate(true, trackDataManager.getListenerTrackPoints_SampledIn(), trackDataManager.getListenerTrackPoints_SampledOut());
|
||||
notifyWaypointsTableUpdate(trackDataManager.getListenerWaypoints());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,14 +276,13 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
*/
|
||||
private void loadDataForListener(TrackDataListener trackDataListener) {
|
||||
Set<TrackDataListener> trackDataListeners = Collections.singleton(trackDataListener);
|
||||
EnumSet<TrackDataType> trackDataTypes = trackDataManager.getTrackDataTypes(trackDataListener);
|
||||
|
||||
if (trackDataTypes.contains(TrackDataType.TRACKS_TABLE)) {
|
||||
if (trackDataManager.listensForTracks(trackDataListener)) {
|
||||
notifyTracksTableUpdate(trackDataListeners);
|
||||
}
|
||||
|
||||
boolean hasSampledIn = trackDataTypes.contains(TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE);
|
||||
boolean hasSampledOut = trackDataTypes.contains(TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE);
|
||||
boolean hasSampledIn = trackDataManager.listensForTrackPoints_SampledIn(trackDataListener);
|
||||
boolean hasSampledOut = trackDataManager.listensForTrackPoints_SampledOut(trackDataListener);
|
||||
if (hasSampledIn || hasSampledOut) {
|
||||
trackDataListener.clearTrackPoints();
|
||||
boolean isOnlyListener = trackDataManager.getNumberOfListeners() == 1;
|
||||
@@ -308,7 +293,7 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
|
||||
}
|
||||
|
||||
if (trackDataTypes.contains(TrackDataType.WAYPOINTS_TABLE)) {
|
||||
if (trackDataManager.listensForWaypoints(trackDataListener)) {
|
||||
notifyWaypointsTableUpdate(trackDataListeners);
|
||||
}
|
||||
}
|
||||
@@ -383,60 +368,60 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
}
|
||||
}
|
||||
|
||||
int localNumLoadedPoints = updateSamplingState ? numLoadedPoints : 0;
|
||||
long localFirstSeenLocationId = updateSamplingState ? firstSeenLocationId : -1L;
|
||||
long localLastSeenLocationId = updateSamplingState ? lastSeenLocationId : -1L;
|
||||
long maxPointId = updateSamplingState ? -1L : lastSeenLocationId;
|
||||
int localNumLoadedTrackPoints = updateSamplingState ? numLoadedPoints : 0;
|
||||
long localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : -1L;
|
||||
long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L;
|
||||
long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId;
|
||||
|
||||
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
|
||||
int samplingFrequency = -1;
|
||||
boolean includeNextPoint = false;
|
||||
|
||||
try (TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenTrackPointIdId + 1, false)) {
|
||||
|
||||
while (locationIterator.hasNext()) {
|
||||
Location location = locationIterator.next();
|
||||
long locationId = locationIterator.getTrackPointId();
|
||||
while (trackPointIterator.hasNext()) {
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
long trackPointId = trackPointIterator.getTrackPointId();
|
||||
|
||||
// Stop if past the last wanted point
|
||||
if (maxPointId != -1L && locationId > maxPointId) {
|
||||
if (maxPointId != -1L && trackPointId > maxPointId) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (localFirstSeenLocationId == -1) {
|
||||
localFirstSeenLocationId = locationId;
|
||||
if (localFirstSeenTrackPointId == -1) {
|
||||
localFirstSeenTrackPointId = trackPointId;
|
||||
}
|
||||
|
||||
if (samplingFrequency == -1) {
|
||||
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenLocationId);
|
||||
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenTrackPointId);
|
||||
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) { //This can be split markers (not anymore supported feature)
|
||||
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) { //This can be split markers (not anymore supported feature)
|
||||
includeNextPoint = true;
|
||||
} else {
|
||||
// Also include the last point if the selected track is not recording.
|
||||
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0) || (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
if (includeNextPoint || (localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
includeNextPoint = false;
|
||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
||||
trackDataListener.onSampledInTrackPoint(location);
|
||||
trackDataListener.onSampledInTrackPoint(trackPoint);
|
||||
}
|
||||
} else {
|
||||
for (TrackDataListener trackDataListener : sampledOutListeners) {
|
||||
trackDataListener.onSampledOutTrackPoint(location);
|
||||
trackDataListener.onSampledOutTrackPoint(trackPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localNumLoadedPoints++;
|
||||
localLastSeenLocationId = locationId;
|
||||
localNumLoadedTrackPoints++;
|
||||
localLastSeenTrackPointIdId = trackPointId;
|
||||
}
|
||||
}
|
||||
|
||||
if (updateSamplingState) {
|
||||
numLoadedPoints = localNumLoadedPoints;
|
||||
firstSeenLocationId = localFirstSeenLocationId;
|
||||
lastSeenLocationId = localLastSeenLocationId;
|
||||
numLoadedPoints = localNumLoadedTrackPoints;
|
||||
firstSeenTrackPointId = localFirstSeenTrackPointId;
|
||||
lastSeenTrackPointId = localLastSeenTrackPointIdId;
|
||||
}
|
||||
|
||||
for (TrackDataListener listener : sampledInListeners) {
|
||||
@@ -449,16 +434,8 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
*/
|
||||
private void resetSamplingState() {
|
||||
numLoadedPoints = 0;
|
||||
firstSeenLocationId = -1L;
|
||||
lastSeenLocationId = -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link DataSource}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private DataSource newDataSource() {
|
||||
return new DataSource(context);
|
||||
firstSeenTrackPointId = -1L;
|
||||
lastSeenTrackPointId = -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -466,6 +443,7 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
*
|
||||
* @param runnable the runnable
|
||||
*/
|
||||
@Deprecated //TODO: Why actually catch this problem: I guess it would be better to fail hard.
|
||||
@VisibleForTesting
|
||||
private void runInHandlerThread(Runnable runnable) {
|
||||
if (handler == null) {
|
||||
@@ -475,13 +453,4 @@ public class TrackDataHub implements DataSourceListener, SharedPreferences.OnSha
|
||||
}
|
||||
handler.post(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value selectedTrackId.
|
||||
*
|
||||
* @return the selectedTrackId
|
||||
*/
|
||||
public long getSelectedTrackId() {
|
||||
return selectedTrackId;
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
|
||||
/**
|
||||
@@ -43,20 +42,20 @@ public interface TrackDataListener {
|
||||
/**
|
||||
* Called when a sampled in track point is read.
|
||||
*
|
||||
* @param location the location
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledInTrackPoint(Location location);
|
||||
void onSampledInTrackPoint(TrackPoint trackPoint);
|
||||
|
||||
/**
|
||||
* Called when a sampled out track point is read.
|
||||
*
|
||||
* @param location the location
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledOutTrackPoint(Location location);
|
||||
void onSampledOutTrackPoint(TrackPoint trackPoint);
|
||||
|
||||
/**
|
||||
* Called when finish sending new track points.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(Location)} and {@link #onSampledOutTrackPoint(Location)}.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(TrackPoint)} and {@link #onSampledOutTrackPoint(TrackPoint)}.
|
||||
*/
|
||||
void onNewTrackPointsDone();
|
||||
|
||||
|
||||
@@ -16,13 +16,7 @@
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -34,84 +28,68 @@ class TrackDataManager {
|
||||
|
||||
private static final String TAG = TrackDataManager.class.getSimpleName();
|
||||
|
||||
private final Map<TrackDataListener, EnumSet<TrackDataType>> listenerToTypesMap = new HashMap<>();
|
||||
private final Set<TrackDataListener> dataListenerTracks = new HashSet<>();
|
||||
private final Set<TrackDataListener> dataListenerWaypoints = new HashSet<>();
|
||||
private final Set<TrackDataListener> dataListenerTrackPoints_SampledIn = new HashSet<>();
|
||||
private final Set<TrackDataListener> dataListenerTrackPoints_SampledOut = new HashSet<>();
|
||||
|
||||
private final Map<TrackDataType, Set<TrackDataListener>> typeToListenersMap = new EnumMap<>(TrackDataType.class);
|
||||
|
||||
TrackDataManager() {
|
||||
for (TrackDataType trackDataType : TrackDataType.values()) {
|
||||
typeToListenersMap.put(trackDataType, new LinkedHashSet<TrackDataListener>());
|
||||
}
|
||||
void registerTrackDataListener(final TrackDataListener trackDataListener, boolean tracksTable, boolean waypointsTable, boolean trackPointsTable_SampleIn, boolean trackPointsTable_SampleOut) {
|
||||
if (tracksTable) dataListenerTracks.add(trackDataListener);
|
||||
if (waypointsTable) dataListenerWaypoints.add(trackDataListener);
|
||||
if (trackPointsTable_SampleIn) dataListenerTrackPoints_SampledIn.add(trackDataListener);
|
||||
if (trackPointsTable_SampleOut) dataListenerTrackPoints_SampledOut.add(trackDataListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
* @param trackDataTypes the track data types the listener is interested
|
||||
*/
|
||||
void registerListener(TrackDataListener listener, EnumSet<TrackDataType> trackDataTypes) {
|
||||
if (listenerToTypesMap.containsKey(listener)) {
|
||||
Log.w(TAG, "Tried to register a listener that is already registered. Ignore.");
|
||||
return;
|
||||
}
|
||||
listenerToTypesMap.put(listener, trackDataTypes);
|
||||
for (TrackDataType trackDataType : trackDataTypes) {
|
||||
typeToListenersMap.get(trackDataType).add(listener);
|
||||
}
|
||||
void unregisterTrackDataListener(TrackDataListener trackDataListener) {
|
||||
dataListenerTracks.add(trackDataListener);
|
||||
dataListenerWaypoints.add(trackDataListener);
|
||||
dataListenerTrackPoints_SampledIn.add(trackDataListener);
|
||||
dataListenerTrackPoints_SampledOut.add(trackDataListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void unregisterListener(TrackDataListener listener) {
|
||||
EnumSet<TrackDataType> removedTypes = listenerToTypesMap.remove(listener);
|
||||
if (removedTypes == null) {
|
||||
Log.w(TAG, "Tried to unregister a listener that is not registered. Ignore.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the listener from the typeToListenersMap
|
||||
for (TrackDataType trackDataType : removedTypes) {
|
||||
typeToListenersMap.get(trackDataType).remove(listener);
|
||||
}
|
||||
boolean hasListeners() {
|
||||
return dataListenerTracks.size() + dataListenerWaypoints.size() + dataListenerTrackPoints_SampledIn.size() + dataListenerTrackPoints_SampledOut.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of {@link TrackDataListener}.
|
||||
*/
|
||||
int getNumberOfListeners() {
|
||||
return listenerToTypesMap.size();
|
||||
Set<TrackDataListener> listener = new HashSet<>();
|
||||
listener.addAll(dataListenerTracks);
|
||||
listener.addAll(dataListenerWaypoints);
|
||||
listener.addAll(dataListenerTrackPoints_SampledIn);
|
||||
listener.addAll(dataListenerTrackPoints_SampledOut);
|
||||
|
||||
return listener.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track data types for a listener.
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
EnumSet<TrackDataType> getTrackDataTypes(TrackDataListener listener) {
|
||||
return listenerToTypesMap.get(listener);
|
||||
boolean listensForTracks(TrackDataListener listener) {
|
||||
return dataListenerTracks.contains(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the listeners for a {@link TrackDataType}.
|
||||
*
|
||||
* @param type the type
|
||||
*/
|
||||
Set<TrackDataListener> getListeners(TrackDataType type) {
|
||||
return typeToListenersMap.get(type);
|
||||
boolean listensForWaypoints(TrackDataListener listener) {
|
||||
return dataListenerWaypoints.contains(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the registered {@link TrackDataType}.
|
||||
*/
|
||||
EnumSet<TrackDataType> getRegisteredTrackDataTypes() {
|
||||
EnumSet<TrackDataType> types = EnumSet.noneOf(TrackDataType.class);
|
||||
for (EnumSet<TrackDataType> value : listenerToTypesMap.values()) {
|
||||
types.addAll(value);
|
||||
}
|
||||
return types;
|
||||
boolean listensForTrackPoints_SampledIn(TrackDataListener listener) {
|
||||
return dataListenerTrackPoints_SampledIn.contains(listener);
|
||||
}
|
||||
|
||||
boolean listensForTrackPoints_SampledOut(TrackDataListener listener) {
|
||||
return dataListenerTrackPoints_SampledOut.contains(listener);
|
||||
}
|
||||
|
||||
Set<TrackDataListener> getListenerTracks() {
|
||||
return dataListenerTracks;
|
||||
}
|
||||
|
||||
Set<TrackDataListener> getListenerWaypoints() {
|
||||
return dataListenerWaypoints;
|
||||
}
|
||||
|
||||
Set<TrackDataListener> getListenerTrackPoints_SampledIn() {
|
||||
return dataListenerTrackPoints_SampledIn;
|
||||
}
|
||||
|
||||
Set<TrackDataListener> getListenerTrackPoints_SampledOut() {
|
||||
return dataListenerTrackPoints_SampledOut;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
/**
|
||||
* Types of track data.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public enum TrackDataType {
|
||||
TRACKS_TABLE, // tracks table changes
|
||||
WAYPOINTS_TABLE, // waypoints table changes
|
||||
SAMPLED_IN_TRACK_POINTS_TABLE, // sampled-in track points table changes
|
||||
SAMPLED_OUT_TRACK_POINTS_TABLE, // sampled-out track points table changes
|
||||
}
|
||||
@@ -16,14 +16,12 @@
|
||||
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* A track.
|
||||
@@ -38,16 +36,12 @@ public class Track {
|
||||
private String description = "";
|
||||
private String category = "";
|
||||
|
||||
/*
|
||||
* The number of location points (present even if the points themselves are not loaded)
|
||||
*/
|
||||
private int numberOfPoints = 0;
|
||||
private String icon = "";
|
||||
|
||||
private TripStatistics tripStatistics = new TripStatistics();
|
||||
private TrackStatistics trackStatistics = new TrackStatistics();
|
||||
|
||||
// Location points (which may not have been loaded)
|
||||
private List<Location> locations = new ArrayList<>();
|
||||
private List<TrackPoint> trackPoints = new ArrayList<>();
|
||||
|
||||
public Track() {
|
||||
}
|
||||
@@ -84,14 +78,6 @@ public class Track {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public int getNumberOfPoints() {
|
||||
return numberOfPoints;
|
||||
}
|
||||
|
||||
public void setNumberOfPoints(int numberOfPoints) {
|
||||
this.numberOfPoints = numberOfPoints;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
@@ -100,24 +86,24 @@ public class Track {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public TripStatistics getTripStatistics() {
|
||||
return tripStatistics;
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
return trackStatistics;
|
||||
}
|
||||
|
||||
public void setTripStatistics(TripStatistics tripStatistics) {
|
||||
this.tripStatistics = tripStatistics;
|
||||
public void setTrackStatistics(TrackStatistics trackStatistics) {
|
||||
this.trackStatistics = trackStatistics;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void addLocation(Location location) {
|
||||
locations.add(location);
|
||||
public void addTrackPoint(TrackPoint location) {
|
||||
trackPoints.add(location);
|
||||
}
|
||||
|
||||
public List<Location> getLocations() {
|
||||
return locations;
|
||||
public List<TrackPoint> getTrackPoints() {
|
||||
return trackPoints;
|
||||
}
|
||||
|
||||
public void setLocations(ArrayList<Location> locations) {
|
||||
this.locations = locations;
|
||||
public void setTrackPoints(ArrayList<TrackPoint> trackPoints) {
|
||||
this.trackPoints = trackPoints;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
|
||||
@@ -24,17 +27,67 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class TrackPoint extends Location {
|
||||
public class TrackPoint {
|
||||
|
||||
private SensorDataSet sensorDataSet = null;
|
||||
private final Location location;
|
||||
|
||||
public TrackPoint(Location location, SensorDataSet sensorDataSet) {
|
||||
super(location);
|
||||
public TrackPoint() {
|
||||
this.location = new Location("");
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull Location location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull Location location, SensorDataSet sensorDataSet) {
|
||||
this.location = location;
|
||||
this.sensorDataSet = sensorDataSet;
|
||||
}
|
||||
|
||||
public TrackPoint(String provider) {
|
||||
super(provider);
|
||||
public TrackPoint(@NonNull TrackPoint trackPoint, SensorDataSet sensorDataSet) {
|
||||
this.location = trackPoint.getLocation();
|
||||
this.sensorDataSet = sensorDataSet;
|
||||
}
|
||||
|
||||
private SensorDataSet sensorDataSet = null;
|
||||
|
||||
public TrackPoint(double latitude, double longitude, Double altitude, long time) {
|
||||
location = new Location(LocationManager.GPS_PROVIDER);
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
if (altitude != null) {
|
||||
location.setAltitude(altitude);
|
||||
}
|
||||
location.setTime(time);
|
||||
}
|
||||
|
||||
public static TrackPoint createPause() {
|
||||
return createPauseWithTime(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public static TrackPoint createPauseWithTime(long time) {
|
||||
Location pause = new Location(LocationManager.GPS_PROVIDER);
|
||||
pause.setLongitude(0);
|
||||
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
|
||||
pause.setTime(time);
|
||||
return new TrackPoint(pause);
|
||||
}
|
||||
|
||||
public static TrackPoint createResume() {
|
||||
return createResumeWithTime(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public static TrackPoint createResumeWithTime(long time) {
|
||||
Location resume = new Location(LocationManager.GPS_PROVIDER);
|
||||
resume.setLongitude(0);
|
||||
resume.setLatitude(TrackPointsColumns.RESUME_LATITUDE);
|
||||
resume.setTime(time);
|
||||
return new TrackPoint(resume);
|
||||
}
|
||||
|
||||
public @NonNull
|
||||
Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public SensorDataSet getSensorDataSet() {
|
||||
@@ -45,8 +98,87 @@ public class TrackPoint extends Location {
|
||||
this.sensorDataSet = sensorDataSet;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
super.reset();
|
||||
sensorDataSet = null;
|
||||
public double getLatitude() {
|
||||
return location.getLatitude();
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
location.setLatitude(latitude);
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return location.getLongitude();
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
location.setLongitude(longitude);
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return location.getTime();
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
location.setTime(time);
|
||||
}
|
||||
|
||||
public boolean hasAltitude() {
|
||||
return location.hasAltitude();
|
||||
}
|
||||
|
||||
public double getAltitude() {
|
||||
return location.getAltitude();
|
||||
}
|
||||
|
||||
public void setAltitude(double altitude) {
|
||||
location.setAltitude(altitude);
|
||||
}
|
||||
|
||||
public boolean hasSpeed() {
|
||||
return location.hasSpeed();
|
||||
}
|
||||
|
||||
public float getSpeed() {
|
||||
return location.getSpeed();
|
||||
}
|
||||
|
||||
public void setSpeed(float speed) {
|
||||
location.setSpeed(speed);
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
return location.hasBearing();
|
||||
}
|
||||
|
||||
public float getBearing() {
|
||||
return location.getBearing();
|
||||
}
|
||||
|
||||
public void setBearing(float bearing) {
|
||||
location.setBearing(bearing);
|
||||
}
|
||||
|
||||
public boolean hasAccuracy() {
|
||||
return location.hasAccuracy();
|
||||
}
|
||||
|
||||
public float getAccuracy() {
|
||||
return location.getAccuracy();
|
||||
}
|
||||
|
||||
public void setAccuracy(float horizontalAccuracy) {
|
||||
location.setAccuracy(horizontalAccuracy);
|
||||
}
|
||||
|
||||
public float distanceTo(@NonNull TrackPoint dest) {
|
||||
return location.distanceTo(dest.getLocation());
|
||||
}
|
||||
|
||||
public float bearingTo(@NonNull TrackPoint dest) {
|
||||
return location.bearingTo(dest.getLocation());
|
||||
}
|
||||
|
||||
public float bearingTo(@NonNull Location dest) {
|
||||
return location.bearingTo(dest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,5 +64,7 @@ public interface TrackPointsColumns extends BaseColumns {
|
||||
+ BEARING + " FLOAT, "
|
||||
+ SENSOR_HEARTRATE + " FLOAT, "
|
||||
+ SENSOR_CADENCE + " FLOAT, "
|
||||
+ SENSOR_POWER + " FLOAT);";
|
||||
+ SENSOR_POWER + " FLOAT)";
|
||||
|
||||
String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")";
|
||||
}
|
||||
|
||||
@@ -38,34 +38,20 @@ public interface TracksColumns extends BaseColumns {
|
||||
String NAME = "name"; // track name
|
||||
String DESCRIPTION = "description"; // track description
|
||||
String CATEGORY = "category"; // track activity type
|
||||
@Deprecated
|
||||
String STARTID = "startid"; // first track point id // TODO UNUSED
|
||||
@Deprecated
|
||||
String STOPID = "stopid"; // last track point id // TODO UNUSED
|
||||
String STARTTIME = "starttime"; // track start time
|
||||
String STOPTIME = "stoptime"; // track stop time
|
||||
String NUMPOINTS = "numpoints"; // number of track points
|
||||
@Deprecated
|
||||
String NUMPOINTS = "numpoints"; // number of track points //TODO UNUSED
|
||||
String TOTALDISTANCE = "totaldistance"; // total distance
|
||||
String TOTALTIME = "totaltime"; // total time
|
||||
String MOVINGTIME = "movingtime"; // moving time
|
||||
|
||||
@Deprecated
|
||||
String MINLAT = "minlat"; // minimum latitude // TODO UNUSED
|
||||
@Deprecated
|
||||
String MAXLAT = "maxlat"; // maximum latitude // TODO UNUSED
|
||||
@Deprecated
|
||||
String MINLON = "minlon"; // minimum longitude //TODO UNUSED
|
||||
@Deprecated
|
||||
String MAXLON = "maxlon"; // maximum longitude //TODO UNUSED
|
||||
|
||||
String AVGSPEED = "avgspeed"; // average speed
|
||||
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed
|
||||
String MAXSPEED = "maxspeed"; // maximum speed
|
||||
String MINELEVATION = "minelevation"; // minimum elevation
|
||||
String MAXELEVATION = "maxelevation"; // maximum elevation
|
||||
String ELEVATIONGAIN = "elevationgain"; // elevation gain
|
||||
String MINGRADE = "mingrade"; // minimum grade
|
||||
String MAXGRADE = "maxgrade"; // maximum grade
|
||||
String ICON = "icon"; // track activity type icon
|
||||
|
||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" // table
|
||||
@@ -73,25 +59,17 @@ public interface TracksColumns extends BaseColumns {
|
||||
+ NAME + " TEXT, " // name
|
||||
+ DESCRIPTION + " TEXT, " // description
|
||||
+ CATEGORY + " TEXT, " // category
|
||||
+ STARTID + " INTEGER, " // start id
|
||||
+ STOPID + " INTEGER, " // stop id
|
||||
+ STARTTIME + " INTEGER, " // start time
|
||||
+ STOPTIME + " INTEGER, " // stop time
|
||||
+ NUMPOINTS + " INTEGER, " // num points
|
||||
+ TOTALDISTANCE + " FLOAT, " // total distance
|
||||
+ TOTALTIME + " INTEGER, " // total time
|
||||
+ MOVINGTIME + " INTEGER, " // moving time
|
||||
+ MINLAT + " INTEGER, " // min latitude
|
||||
+ MAXLAT + " INTEGER, " // max latitude
|
||||
+ MINLON + " INTEGER, " // min longitude
|
||||
+ MAXLON + " INTEGER, " // max longitude
|
||||
+ AVGSPEED + " FLOAT, " // average speed
|
||||
+ AVGMOVINGSPEED + " FLOAT, " // average moving speed
|
||||
+ MAXSPEED + " FLOAT, " // max speed
|
||||
+ MINELEVATION + " FLOAT, " // min elevation
|
||||
+ MAXELEVATION + " FLOAT, " // max elevation
|
||||
+ ELEVATIONGAIN + " FLOAT, " // elevation gain
|
||||
+ MINGRADE + " FLOAT, " // min grade
|
||||
+ MAXGRADE + " FLOAT, " // max grade
|
||||
+ ICON + " TEXT);"; // icon
|
||||
+ ICON + " TEXT)"; // icon
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ package de.dennisguse.opentracks.content.data;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* A waypoint.
|
||||
*
|
||||
@@ -38,10 +41,15 @@ public final class Waypoint {
|
||||
private Location location = null;
|
||||
private String photoUrl = "";
|
||||
|
||||
@VisibleForTesting
|
||||
public Waypoint() {
|
||||
}
|
||||
|
||||
public Waypoint(String name, String description, String category, String icon, long trackId, double length, long duration, Location location, String photoUrl) {
|
||||
public Waypoint(Location location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public Waypoint(String name, String description, String category, String icon, long trackId, double length, long duration, @NonNull Location location, String photoUrl) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.category = category;
|
||||
@@ -117,14 +125,11 @@ public final class Waypoint {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
public @NonNull
|
||||
Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(Location location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getPhotoUrl() {
|
||||
return photoUrl;
|
||||
}
|
||||
|
||||
@@ -40,49 +40,17 @@ public interface WaypointsColumns extends BaseColumns {
|
||||
String CATEGORY = "category"; // waypoint category
|
||||
String ICON = "icon"; // waypoint icon
|
||||
String TRACKID = "trackid"; // track id
|
||||
@Deprecated
|
||||
String TYPE = "type"; // type // TODO UNUSED
|
||||
|
||||
String LENGTH = "length"; // length of the track (without smoothing)
|
||||
String DURATION = "duration"; // total duration of the track (not from last waypoint)
|
||||
String STARTTIME = "starttime"; // start time of the trip statistics
|
||||
@Deprecated
|
||||
String STARTID = "startid"; // start track point id // TODO UNUSED
|
||||
@Deprecated
|
||||
String STOPID = "stopid"; // stop track point id // TODO UNUSED
|
||||
String DURATION = "duration"; // total duration of the track from the beginning until now
|
||||
|
||||
String LONGITUDE = "longitude"; // longitude
|
||||
String LATITUDE = "latitude"; // latitude
|
||||
String TIME = "time"; // time
|
||||
String ALTITUDE = "elevation"; // altitude
|
||||
String ACCURACY = "accuracy"; // accuracy
|
||||
String SPEED = "speed"; // speed
|
||||
String BEARING = "bearing"; // bearing
|
||||
|
||||
@Deprecated
|
||||
String TOTALDISTANCE = "totaldistance"; // total distance
|
||||
@Deprecated
|
||||
String TOTALTIME = "totaltime"; // total time
|
||||
@Deprecated
|
||||
String MOVINGTIME = "movingtime"; // moving time
|
||||
@Deprecated
|
||||
String AVGSPEED = "avgspeed"; // average speed
|
||||
|
||||
@Deprecated
|
||||
String AVGMOVINGSPEED = "avgmovingspeed"; // average moving speed // TODO UNUSED
|
||||
@Deprecated
|
||||
String MAXSPEED = "maxspeed"; // max speed // TODO UNUSED
|
||||
@Deprecated
|
||||
String MINELEVATION = "minelevation"; // min elevation // TODO UNUSED
|
||||
@Deprecated
|
||||
String MAXELEVATION = "maxelevation"; // max elevation // TODO UNUSED
|
||||
@Deprecated
|
||||
String ELEVATIONGAIN = "elevationgain"; // elevation gain // TODO UNUSED
|
||||
@Deprecated
|
||||
String MINGRADE = "mingrade"; // min grade // TODO UNUSED
|
||||
@Deprecated
|
||||
String MAXGRADE = "maxgrade"; // max grade // TODO UNUSED
|
||||
|
||||
String PHOTOURL = "photoUrl"; // url for the photo
|
||||
|
||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
@@ -92,30 +60,16 @@ public interface WaypointsColumns extends BaseColumns {
|
||||
+ CATEGORY + " TEXT, "
|
||||
+ ICON + " TEXT, "
|
||||
+ TRACKID + " INTEGER, "
|
||||
+ TYPE + " INTEGER, "
|
||||
+ LENGTH + " FLOAT, "
|
||||
+ DURATION + " INTEGER, "
|
||||
+ STARTTIME + " INTEGER, "
|
||||
+ STARTID + " INTEGER, "
|
||||
+ STOPID + " INTEGER, "
|
||||
+ LONGITUDE + " INTEGER, "
|
||||
+ LATITUDE + " INTEGER, "
|
||||
+ TIME + " INTEGER, "
|
||||
+ ALTITUDE + " FLOAT, "
|
||||
+ ACCURACY + " FLOAT, "
|
||||
+ SPEED + " FLOAT, "
|
||||
+ BEARING + " FLOAT, "
|
||||
+ TOTALDISTANCE + " FLOAT, "
|
||||
+ TOTALTIME + " INTEGER, "
|
||||
+ MOVINGTIME + " INTEGER, "
|
||||
+ AVGSPEED + " FLOAT, "
|
||||
+ AVGMOVINGSPEED + " FLOAT, "
|
||||
+ MAXSPEED + " FLOAT, "
|
||||
+ MINELEVATION + " FLOAT, "
|
||||
+ MAXELEVATION + " FLOAT, "
|
||||
+ ELEVATIONGAIN + " FLOAT, "
|
||||
+ MINGRADE + " FLOAT, "
|
||||
+ MAXGRADE + " FLOAT, "
|
||||
+ PHOTOURL + " TEXT"
|
||||
+ ");";
|
||||
+ ")";
|
||||
|
||||
String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")";
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
/**
|
||||
@@ -69,7 +69,7 @@ public class ContentProviderUtils {
|
||||
* Maximum number of waypoints that will be loaded at one time.
|
||||
*/
|
||||
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
|
||||
public static final String ID_SEPARATOR = ",";
|
||||
private static final String ID_SEPARATOR = ",";
|
||||
|
||||
private final IContentResolver contentResolver;
|
||||
private int defaultCursorBatchSize = 2000;
|
||||
@@ -111,7 +111,6 @@ public class ContentProviderUtils {
|
||||
int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
|
||||
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
|
||||
int stopTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME);
|
||||
int numPointsIndex = cursor.getColumnIndexOrThrow(TracksColumns.NUMPOINTS);
|
||||
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
|
||||
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
|
||||
int movingTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME);
|
||||
@@ -119,12 +118,10 @@ public class ContentProviderUtils {
|
||||
int minElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION);
|
||||
int maxElevationIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION);
|
||||
int elevationGainIndex = cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN);
|
||||
int minGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINGRADE);
|
||||
int maxGradeIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXGRADE);
|
||||
int iconIndex = cursor.getColumnIndexOrThrow(TracksColumns.ICON);
|
||||
|
||||
Track track = new Track();
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
if (!cursor.isNull(idIndex)) {
|
||||
track.setId(cursor.getLong(idIndex));
|
||||
}
|
||||
@@ -138,40 +135,31 @@ public class ContentProviderUtils {
|
||||
track.setCategory(cursor.getString(categoryIndex));
|
||||
}
|
||||
if (!cursor.isNull(startTimeIndex)) {
|
||||
tripStatistics.setStartTime(cursor.getLong(startTimeIndex));
|
||||
trackStatistics.setStartTime_ms(cursor.getLong(startTimeIndex));
|
||||
}
|
||||
if (!cursor.isNull(stopTimeIndex)) {
|
||||
tripStatistics.setStopTime(cursor.getLong(stopTimeIndex));
|
||||
}
|
||||
if (!cursor.isNull(numPointsIndex)) {
|
||||
track.setNumberOfPoints(cursor.getInt(numPointsIndex));
|
||||
trackStatistics.setStopTime_ms(cursor.getLong(stopTimeIndex));
|
||||
}
|
||||
if (!cursor.isNull(totalDistanceIndex)) {
|
||||
tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex));
|
||||
trackStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex));
|
||||
}
|
||||
if (!cursor.isNull(totalTimeIndex)) {
|
||||
tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex));
|
||||
trackStatistics.setTotalTime(cursor.getLong(totalTimeIndex));
|
||||
}
|
||||
if (!cursor.isNull(movingTimeIndex)) {
|
||||
tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex));
|
||||
trackStatistics.setMovingTime(cursor.getLong(movingTimeIndex));
|
||||
}
|
||||
if (!cursor.isNull(maxSpeedIndex)) {
|
||||
tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex));
|
||||
trackStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex));
|
||||
}
|
||||
if (!cursor.isNull(minElevationIndex)) {
|
||||
tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex));
|
||||
trackStatistics.setMinElevation(cursor.getFloat(minElevationIndex));
|
||||
}
|
||||
if (!cursor.isNull(maxElevationIndex)) {
|
||||
tripStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex));
|
||||
trackStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex));
|
||||
}
|
||||
if (!cursor.isNull(elevationGainIndex)) {
|
||||
tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex));
|
||||
}
|
||||
if (!cursor.isNull(minGradeIndex)) {
|
||||
tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex));
|
||||
}
|
||||
if (!cursor.isNull(maxGradeIndex)) {
|
||||
tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex));
|
||||
trackStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex));
|
||||
}
|
||||
if (!cursor.isNull(iconIndex)) {
|
||||
track.setIcon(cursor.getString(iconIndex));
|
||||
@@ -207,7 +195,6 @@ public class ContentProviderUtils {
|
||||
|
||||
/**
|
||||
* Deletes track points and waypoints of a track.
|
||||
* Assumes {@link TracksColumns#NUMPOINTS} will be updated by the caller.
|
||||
*
|
||||
* @param trackId the track id
|
||||
*/
|
||||
@@ -321,7 +308,7 @@ public class ContentProviderUtils {
|
||||
|
||||
private ContentValues createContentValues(Track track) {
|
||||
ContentValues values = new ContentValues();
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
|
||||
// Value < 0 indicates no id is available
|
||||
if (track.getId() >= 0) {
|
||||
@@ -330,20 +317,17 @@ public class ContentProviderUtils {
|
||||
values.put(TracksColumns.NAME, track.getName());
|
||||
values.put(TracksColumns.DESCRIPTION, track.getDescription());
|
||||
values.put(TracksColumns.CATEGORY, track.getCategory());
|
||||
values.put(TracksColumns.STARTTIME, tripStatistics.getStartTime());
|
||||
values.put(TracksColumns.STOPTIME, tripStatistics.getStopTime());
|
||||
values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints());
|
||||
values.put(TracksColumns.TOTALDISTANCE, tripStatistics.getTotalDistance());
|
||||
values.put(TracksColumns.TOTALTIME, tripStatistics.getTotalTime());
|
||||
values.put(TracksColumns.MOVINGTIME, tripStatistics.getMovingTime());
|
||||
values.put(TracksColumns.AVGSPEED, tripStatistics.getAverageSpeed());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, tripStatistics.getAverageMovingSpeed());
|
||||
values.put(TracksColumns.MAXSPEED, tripStatistics.getMaxSpeed());
|
||||
values.put(TracksColumns.MINELEVATION, tripStatistics.getMinElevation());
|
||||
values.put(TracksColumns.MAXELEVATION, tripStatistics.getMaxElevation());
|
||||
values.put(TracksColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain());
|
||||
values.put(TracksColumns.MINGRADE, tripStatistics.getMinGrade());
|
||||
values.put(TracksColumns.MAXGRADE, tripStatistics.getMaxGrade());
|
||||
values.put(TracksColumns.STARTTIME, trackStatistics.getStartTime_ms());
|
||||
values.put(TracksColumns.STOPTIME, trackStatistics.getStopTime_ms());
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance());
|
||||
values.put(TracksColumns.TOTALTIME, trackStatistics.getTotalTime());
|
||||
values.put(TracksColumns.MOVINGTIME, trackStatistics.getMovingTime());
|
||||
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed());
|
||||
values.put(TracksColumns.MINELEVATION, trackStatistics.getMinElevation());
|
||||
values.put(TracksColumns.MAXELEVATION, trackStatistics.getMaxElevation());
|
||||
values.put(TracksColumns.ELEVATIONGAIN, trackStatistics.getTotalElevationGain());
|
||||
values.put(TracksColumns.ICON, track.getIcon());
|
||||
|
||||
return values;
|
||||
@@ -369,11 +353,28 @@ public class ContentProviderUtils {
|
||||
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
|
||||
int altitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ALTITUDE);
|
||||
int accuracyIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY);
|
||||
int speedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.SPEED);
|
||||
int bearingIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING);
|
||||
int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL);
|
||||
|
||||
Waypoint waypoint = new Waypoint();
|
||||
Location location = new Location("");
|
||||
if (!cursor.isNull(longitudeIndex) && !cursor.isNull(latitudeIndex)) {
|
||||
location.setLongitude(((double) cursor.getInt(longitudeIndex)) / 1E6);
|
||||
location.setLatitude(((double) cursor.getInt(latitudeIndex)) / 1E6);
|
||||
}
|
||||
if (!cursor.isNull(timeIndex)) {
|
||||
location.setTime(cursor.getLong(timeIndex));
|
||||
}
|
||||
if (!cursor.isNull(altitudeIndex)) {
|
||||
location.setAltitude(cursor.getFloat(altitudeIndex));
|
||||
}
|
||||
if (!cursor.isNull(accuracyIndex)) {
|
||||
location.setAccuracy(cursor.getFloat(accuracyIndex));
|
||||
}
|
||||
if (!cursor.isNull(bearingIndex)) {
|
||||
location.setBearing(cursor.getFloat(bearingIndex));
|
||||
}
|
||||
|
||||
Waypoint waypoint = new Waypoint(location);
|
||||
|
||||
if (!cursor.isNull(idIndex)) {
|
||||
waypoint.setId(cursor.getLong(idIndex));
|
||||
@@ -400,28 +401,6 @@ public class ContentProviderUtils {
|
||||
waypoint.setDuration(cursor.getLong(durationIndex));
|
||||
}
|
||||
|
||||
Location location = new Location("");
|
||||
if (!cursor.isNull(longitudeIndex) && !cursor.isNull(latitudeIndex)) {
|
||||
location.setLongitude(((double) cursor.getInt(longitudeIndex)) / 1E6);
|
||||
location.setLatitude(((double) cursor.getInt(latitudeIndex)) / 1E6);
|
||||
}
|
||||
if (!cursor.isNull(timeIndex)) {
|
||||
location.setTime(cursor.getLong(timeIndex));
|
||||
}
|
||||
if (!cursor.isNull(altitudeIndex)) {
|
||||
location.setAltitude(cursor.getFloat(altitudeIndex));
|
||||
}
|
||||
if (!cursor.isNull(accuracyIndex)) {
|
||||
location.setAccuracy(cursor.getFloat(accuracyIndex));
|
||||
}
|
||||
if (!cursor.isNull(speedIndex)) {
|
||||
location.setSpeed(cursor.getFloat(speedIndex));
|
||||
}
|
||||
if (!cursor.isNull(bearingIndex)) {
|
||||
location.setBearing(cursor.getFloat(bearingIndex));
|
||||
}
|
||||
waypoint.setLocation(location);
|
||||
|
||||
if (!cursor.isNull(photoUrlIndex)) {
|
||||
waypoint.setPhotoUrl(cursor.getString(photoUrlIndex));
|
||||
}
|
||||
@@ -613,9 +592,6 @@ public class ContentProviderUtils {
|
||||
if (location.hasAccuracy()) {
|
||||
values.put(WaypointsColumns.ACCURACY, location.getAccuracy());
|
||||
}
|
||||
if (location.hasSpeed()) {
|
||||
values.put(WaypointsColumns.SPEED, location.getSpeed());
|
||||
}
|
||||
if (location.hasBearing()) {
|
||||
values.put(WaypointsColumns.BEARING, location.getBearing());
|
||||
}
|
||||
@@ -645,34 +621,63 @@ public class ContentProviderUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts multiple track points.
|
||||
* Fills a {@link TrackPoint} from a cursor.
|
||||
*
|
||||
* @param locations an array of locations
|
||||
* @param length the number of locations (from the beginning of the array) to
|
||||
* insert, or -1 for all of them
|
||||
* @param trackId the track id
|
||||
* @return the number of points inserted
|
||||
* @param cursor the cursor pointing to a trackPoint.
|
||||
* @param indexes the cached track points indexes
|
||||
*/
|
||||
public int bulkInsertTrackPoint(Location[] locations, int length, long trackId) {
|
||||
if (length == -1) {
|
||||
length = locations.length;
|
||||
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
|
||||
if (!cursor.isNull(indexes.longitudeIndex)) {
|
||||
trackPoint.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6);
|
||||
}
|
||||
ContentValues[] values = new ContentValues[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
values[i] = createContentValues(locations[i], trackId);
|
||||
if (!cursor.isNull(indexes.latitudeIndex)) {
|
||||
trackPoint.setLatitude(((double) cursor.getInt(indexes.latitudeIndex)) / 1E6);
|
||||
}
|
||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||
if (!cursor.isNull(indexes.timeIndex)) {
|
||||
trackPoint.setTime(cursor.getLong(indexes.timeIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.altitudeIndex)) {
|
||||
trackPoint.setAltitude(cursor.getFloat(indexes.altitudeIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.accuracyIndex)) {
|
||||
trackPoint.setAccuracy(cursor.getFloat(indexes.accuracyIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.speedIndex)) {
|
||||
trackPoint.setSpeed(cursor.getFloat(indexes.speedIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.bearingIndex)) {
|
||||
trackPoint.setBearing(cursor.getFloat(indexes.bearingIndex));
|
||||
}
|
||||
|
||||
float heartRate = cursor.isNull(indexes.sensorHeartRateIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorHeartRateIndex);
|
||||
float cadence = cursor.isNull(indexes.sensorCadenceIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorCadenceIndex);
|
||||
float power = cursor.isNull(indexes.sensorPowerIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorPowerIndex);
|
||||
|
||||
trackPoint.setSensorDataSet(new SensorDataSet(heartRate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, trackPoint.getTime()));
|
||||
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location object from a cursor.
|
||||
* Inserts multiple trackPoints points.
|
||||
*
|
||||
* @param cursor the cursor pointing to the location
|
||||
* @param trackPoints an array of trackPoints
|
||||
* @param length the number of trackPoints (from the beginning of the array) to
|
||||
* insert, or -1 for all of them
|
||||
* @param trackId the trackPoints id
|
||||
* @return the number of points inserted
|
||||
*/
|
||||
public Location createTrackPoint(Cursor cursor) {
|
||||
Location location = new TrackPoint("");
|
||||
fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor), location);
|
||||
return location;
|
||||
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, int length, long trackId) {
|
||||
if (length == -1) {
|
||||
length = trackPoints.length;
|
||||
}
|
||||
ContentValues[] values = new ContentValues[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
values[i] = createContentValues(trackPoints[i], trackId);
|
||||
}
|
||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -745,21 +750,12 @@ public class ContentProviderUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last valid location for a track.
|
||||
* Returns null if it doesn't exist.
|
||||
* Creates a {@link TrackPoint} object from a cursor.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param cursor the cursor pointing to the location
|
||||
*/
|
||||
@Deprecated
|
||||
public Location getLastValidTrackPoint(long trackId) {
|
||||
if (trackId < 0) {
|
||||
return null;
|
||||
}
|
||||
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from "
|
||||
+ TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND "
|
||||
+ TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
|
||||
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
||||
return findTrackPointBy(selection, selectionArgs);
|
||||
public TrackPoint createTrackPoint(Cursor cursor) {
|
||||
return fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -797,99 +793,71 @@ public class ContentProviderUtils {
|
||||
return getTrackPointCursor(null, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fills a track point from a cursor.
|
||||
* Gets the last valid location for a track.
|
||||
* Returns null if it doesn't exist.
|
||||
*
|
||||
* @param cursor the cursor pointing to a location.
|
||||
* @param indexes the cached track points indexes
|
||||
* @param location the track point
|
||||
* @param trackId the track id
|
||||
*/
|
||||
static void fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes, Location location) {
|
||||
location.reset();
|
||||
|
||||
if (!cursor.isNull(indexes.longitudeIndex)) {
|
||||
location.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6);
|
||||
}
|
||||
if (!cursor.isNull(indexes.latitudeIndex)) {
|
||||
location.setLatitude(((double) cursor.getInt(indexes.latitudeIndex)) / 1E6);
|
||||
}
|
||||
if (!cursor.isNull(indexes.timeIndex)) {
|
||||
location.setTime(cursor.getLong(indexes.timeIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.altitudeIndex)) {
|
||||
location.setAltitude(cursor.getFloat(indexes.altitudeIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.accuracyIndex)) {
|
||||
location.setAccuracy(cursor.getFloat(indexes.accuracyIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.speedIndex)) {
|
||||
location.setSpeed(cursor.getFloat(indexes.speedIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.bearingIndex)) {
|
||||
location.setBearing(cursor.getFloat(indexes.bearingIndex));
|
||||
}
|
||||
if (location instanceof TrackPoint) {
|
||||
TrackPoint sensorDataSetLocation = (TrackPoint) location;
|
||||
|
||||
float heartRate = cursor.isNull(indexes.sensorHeartRateIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorHeartRateIndex);
|
||||
float cadence = cursor.isNull(indexes.sensorCadenceIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorCadenceIndex);
|
||||
float power = cursor.isNull(indexes.sensorPowerIndex) ? SensorDataSet.DATA_UNAVAILABLE : cursor.getFloat(indexes.sensorPowerIndex);
|
||||
|
||||
sensorDataSetLocation.setSensorDataSet(new SensorDataSet(heartRate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime()));
|
||||
@Deprecated
|
||||
public TrackPoint getLastValidTrackPoint(long trackId) {
|
||||
if (trackId < 0) {
|
||||
return null;
|
||||
}
|
||||
String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + ") from "
|
||||
+ TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND "
|
||||
+ TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
|
||||
String[] selectionArgs = new String[]{Long.toString(trackId)};
|
||||
return findTrackPointBy(selection, selectionArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a track point.
|
||||
*
|
||||
* @param location the location
|
||||
* @param trackPoint the trackPoint
|
||||
* @param trackId the track id
|
||||
* @return the content provider URI of the inserted track point
|
||||
*/
|
||||
public Uri insertTrackPoint(Location location, long trackId) {
|
||||
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(location, trackId));
|
||||
public Uri insertTrackPoint(TrackPoint trackPoint, long trackId) {
|
||||
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(trackPoint, trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link ContentValues} for a {@link Location}.
|
||||
* Creates the {@link ContentValues} for a {@link TrackPoint}.
|
||||
*
|
||||
* @param location the location
|
||||
* @param trackPoint the trackPoint
|
||||
* @param trackId the track id
|
||||
*/
|
||||
private ContentValues createContentValues(Location location, long trackId) {
|
||||
private ContentValues createContentValues(TrackPoint trackPoint, long trackId) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(TrackPointsColumns.TRACKID, trackId);
|
||||
values.put(TrackPointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6));
|
||||
values.put(TrackPointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6));
|
||||
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
|
||||
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
|
||||
|
||||
values.put(TrackPointsColumns.TIME, location.getTime());
|
||||
if (location.hasAltitude()) {
|
||||
values.put(TrackPointsColumns.ALTITUDE, location.getAltitude());
|
||||
values.put(TrackPointsColumns.TIME, trackPoint.getTime());
|
||||
if (trackPoint.hasAltitude()) {
|
||||
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude());
|
||||
}
|
||||
if (location.hasAccuracy()) {
|
||||
values.put(TrackPointsColumns.ACCURACY, location.getAccuracy());
|
||||
if (trackPoint.hasAccuracy()) {
|
||||
values.put(TrackPointsColumns.ACCURACY, trackPoint.getAccuracy());
|
||||
}
|
||||
if (location.hasSpeed()) {
|
||||
values.put(TrackPointsColumns.SPEED, location.getSpeed());
|
||||
if (trackPoint.hasSpeed()) {
|
||||
values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed());
|
||||
}
|
||||
if (location.hasBearing()) {
|
||||
values.put(TrackPointsColumns.BEARING, location.getBearing());
|
||||
if (trackPoint.hasBearing()) {
|
||||
values.put(TrackPointsColumns.BEARING, trackPoint.getBearing());
|
||||
}
|
||||
|
||||
//SensorData
|
||||
if (location instanceof TrackPoint) {
|
||||
TrackPoint sensorDataSetLocation = (TrackPoint) location;
|
||||
SensorDataSet sensorDataSet = sensorDataSetLocation.getSensorDataSet();
|
||||
if (sensorDataSet != null && sensorDataSet.hasHeartRate()) {
|
||||
values.put(TrackPointsColumns.SENSOR_HEARTRATE, sensorDataSetLocation.getSensorDataSet().getHeartRate());
|
||||
}
|
||||
if (sensorDataSet != null && sensorDataSet.hasCadence()) {
|
||||
values.put(TrackPointsColumns.SENSOR_CADENCE, sensorDataSetLocation.getSensorDataSet().getCadence());
|
||||
}
|
||||
if (sensorDataSet != null && sensorDataSet.hasPower()) {
|
||||
values.put(TrackPointsColumns.SENSOR_POWER, sensorDataSetLocation.getSensorDataSet().getPower());
|
||||
}
|
||||
SensorDataSet sensorDataSet = trackPoint.getSensorDataSet();
|
||||
if (sensorDataSet != null && sensorDataSet.hasHeartRate()) {
|
||||
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getSensorDataSet().getHeartRate());
|
||||
}
|
||||
if (sensorDataSet != null && sensorDataSet.hasCadence()) {
|
||||
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getSensorDataSet().getCadence());
|
||||
}
|
||||
if (sensorDataSet != null && sensorDataSet.hasPower()) {
|
||||
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getSensorDataSet().getPower());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
@@ -898,19 +866,18 @@ public class ContentProviderUtils {
|
||||
* Creates a new read-only iterator over a given track's points.
|
||||
* It provides a lightweight way of iterating over long tracks without failing due to the underlying cursor limitations.
|
||||
* Since it's a read-only iterator, {@link Iterator#remove()} always throws {@link UnsupportedOperationException}.
|
||||
* Each call to {@link TrackPointIterator#next()} may advance to the next DB record, and if so, the iterator calls {@link TrackPointFactory#createLocation()} and populates it with information retrieved from the record.
|
||||
* Each call to {@link TrackPointIterator#next()} may advance to the next DB record.
|
||||
* When done with iteration, {@link TrackPointIterator#close()} must be called.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @param startTrackPointId the starting track point id. -1L to ignore
|
||||
* @param descending true to sort the result in descending order (latest location first)
|
||||
* @param trackPointFactory the location factory
|
||||
*/
|
||||
public TrackPointIterator getTrackPointLocationIterator(final long trackId, final long startTrackPointId, final boolean descending, final TrackPointFactory trackPointFactory) {
|
||||
return new TrackPointIterator(this, trackId, startTrackPointId, descending, trackPointFactory);
|
||||
public TrackPointIterator getTrackPointLocationIterator(final long trackId, final long startTrackPointId, final boolean descending) {
|
||||
return new TrackPointIterator(this, trackId, startTrackPointId, descending);
|
||||
}
|
||||
|
||||
private Location findTrackPointBy(String selection, String[] selectionArgs) {
|
||||
private TrackPoint findTrackPointBy(String selection, String[] selectionArgs) {
|
||||
try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) {
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
return createTrackPoint(cursor);
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.content.provider;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
@@ -14,13 +15,15 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
|
||||
* Database helper for creating and upgrading the database.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
@VisibleForTesting
|
||||
static final int DATABASE_VERSION = 23;
|
||||
private static final int DATABASE_VERSION = 25;
|
||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||
|
||||
@VisibleForTesting
|
||||
static final String DATABASE_NAME = "database.db";
|
||||
private static final String DATABASE_NAME = "database.db";
|
||||
|
||||
|
||||
public CustomSQLiteOpenHelper(Context context) {
|
||||
this(context, DATABASE_NAME);
|
||||
@@ -31,14 +34,112 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
super(context, databaseName, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public CustomSQLiteOpenHelper(Context context, String databaseName, int databaseVersion) {
|
||||
super(context, databaseName, null, databaseVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(TrackPointsColumns.CREATE_TABLE);
|
||||
db.execSQL(TrackPointsColumns.CREATE_TABLE_INDEX);
|
||||
|
||||
db.execSQL(TracksColumns.CREATE_TABLE);
|
||||
|
||||
db.execSQL(WaypointsColumns.CREATE_TABLE);
|
||||
db.execSQL(WaypointsColumns.CREATE_TABLE_INDEX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
for (int toVersion = oldVersion + 1; toVersion <= newVersion; toVersion++) {
|
||||
Log.i(TAG, "Upgrade from " + oldVersion + " to " + toVersion);
|
||||
switch (toVersion) {
|
||||
case 24:
|
||||
upgradeFrom23to24(db);
|
||||
break;
|
||||
case 25:
|
||||
upgradeFrom24to25(db);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
for (int toVersion = oldVersion - 1; toVersion >= newVersion; toVersion--) {
|
||||
Log.i(TAG, "Downgrade from " + oldVersion + " to " + toVersion);
|
||||
switch (toVersion) {
|
||||
case 23:
|
||||
downgradeFrom24to23(db);
|
||||
break;
|
||||
case 24:
|
||||
downgradeFrom25to24(db);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade from database version 23 (waypoints, tracks): remove unused columns.
|
||||
* SQLite3 does not support drop columns; therefore new tables are created and data is copied.
|
||||
*/
|
||||
private void upgradeFrom23to24(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
||||
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, icon TEXT)");
|
||||
db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, icon FROM tracks_old");
|
||||
db.execSQL("DROP TABLE tracks_old");
|
||||
|
||||
db.execSQL("ALTER TABLE waypoints RENAME TO waypoints_old");
|
||||
db.execSQL("CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, length FLOAT, duration INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT)");
|
||||
db.execSQL("INSERT INTO waypoints SELECT _id, name, description, category, icon, trackid, length, duration, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM waypoints_old");
|
||||
db.execSQL("DROP TABLE waypoints_old");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void downgradeFrom24to23(SQLiteDatabase db) {
|
||||
//Not needed as the delete columns did not contain any data
|
||||
db.beginTransaction();
|
||||
db.execSQL("ALTER TABLE tracks RENAME TO tracks_old");
|
||||
db.execSQL("CREATE TABLE tracks (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, starttime INTEGER, stoptime INTEGER, numpoints INTEGER, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, icon TEXT)");
|
||||
db.execSQL("INSERT INTO tracks SELECT _id, name, description, category, starttime, stoptime, numpoints, totaldistance, totaltime, movingtime, avgspeed, avgmovingspeed, maxspeed, minelevation, maxelevation, elevationgain, 0, 0, icon FROM tracks_old");
|
||||
db.execSQL("DROP TABLE tracks_old");
|
||||
|
||||
db.execSQL("ALTER TABLE waypoints RENAME TO waypoints_old");
|
||||
db.execSQL("CREATE TABLE waypoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER, type INTEGER, length FLOAT, duration INTEGER, starttime INTEGER, startid INTEGER, stopid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, totaldistance FLOAT, totaltime INTEGER, movingtime INTEGER, avgspeed FLOAT, avgmovingspeed FLOAT, maxspeed FLOAT, minelevation FLOAT, maxelevation FLOAT, elevationgain FLOAT, mingrade FLOAT, maxgrade FLOAT, photoUrl TEXT)");
|
||||
db.execSQL("INSERT INTO waypoints SELECT _id, name, description, category, icon, trackid, 0, length, duration, 0, 0, 0, longitude, latitude, time, elevation, accuracy, 0, bearing, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, photoUrl FROM waypoints_old");
|
||||
db.execSQL("DROP TABLE waypoints_old");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void upgradeFrom24to25(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
|
||||
db.execSQL("CREATE INDEX waypoints_trackid_index ON waypoints(trackid)");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void downgradeFrom25to24(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("DROP INDEX trackpoints_trackid_index");
|
||||
db.execSQL("DROP INDEX waypoints_trackid_index");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.android.IContentResolver;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -42,7 +43,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
||||
|
||||
private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
|
||||
|
||||
public static final String TAG = ShareContentProvider.class.getSimpleName();
|
||||
private static final String TAG = ShareContentProvider.class.getSimpleName();
|
||||
|
||||
private static final int URI_GPX = 0;
|
||||
private static final int URI_KML_ONLY = 1;
|
||||
@@ -58,30 +59,30 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
||||
private static final String TRACKID_DELIMITER = "_";
|
||||
|
||||
static {
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*", URI_GPX);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.GPX.getName() + "/*/*", URI_GPX);
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_ONLY_TRACK.getName() + "/*", URI_KML_ONLY);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL.getName() + "/*", URI_KML_WITH_TRACKDETAIL);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_ONLY_TRACK.getName() + "/*", URI_KMZ_ONLY_TRACK);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_ONLY_TRACK.getName() + "/*/*", URI_KML_ONLY);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL.getName() + "/*/*", URI_KML_WITH_TRACKDETAIL);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KML_WITH_TRACKDETAIL_SENSORDATA);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_ONLY_TRACK.getName() + "/*/*", URI_KMZ_ONLY_TRACK);
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
||||
}
|
||||
|
||||
public static Pair<Uri, String> createURI(long[] trackIds, @NonNull TrackFileFormat trackFileFormat) {
|
||||
public static Pair<Uri, String> createURI(long[] trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
|
||||
if (trackIds.length == 0) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
StringBuilder trackIdBuilder = new StringBuilder();
|
||||
for (long trackId : trackIds) {
|
||||
builder.append(trackId).append(TRACKID_DELIMITER);
|
||||
trackIdBuilder.append(trackId).append(TRACKID_DELIMITER);
|
||||
}
|
||||
builder.deleteCharAt(builder.lastIndexOf(TRACKID_DELIMITER));
|
||||
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
|
||||
|
||||
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + builder + "." + trackFileFormat.getExtension());
|
||||
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getName() + "/" + trackIdBuilder + "/" + Uri.encode(trackName) + "." + trackFileFormat.getExtension());
|
||||
String mime = getTypeMime(uri);
|
||||
|
||||
Log.d(TAG, "Created uri " + uri.toString() + " with MIME " + mime);
|
||||
@@ -89,18 +90,18 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
||||
return new Pair<>(uri, mime);
|
||||
}
|
||||
|
||||
private static long[] parseURI(Uri uri) {
|
||||
String lastPathSegment = uri.getLastPathSegment();
|
||||
if (lastPathSegment == null) {
|
||||
static long[] parseURI(Uri uri) {
|
||||
List<String> uriPaths = uri.getPathSegments();
|
||||
if (uriPaths == null || uriPaths.size() < 2) {
|
||||
Log.d(TAG, "URI does not contain any trackIds.");
|
||||
return new long[]{};
|
||||
}
|
||||
|
||||
String fileExtension = "." + getTrackFileFormat(uri).getExtension();
|
||||
String[] lastPathSegmentSplit = lastPathSegment.replace(fileExtension, "").split(TRACKID_DELIMITER);
|
||||
String[] uriTrackIds = uriPaths.get(2).split(TRACKID_DELIMITER);
|
||||
|
||||
long[] trackIds = new long[lastPathSegmentSplit.length];
|
||||
long[] trackIds = new long[uriTrackIds.length];
|
||||
for (int i = 0; i < trackIds.length; i++) {
|
||||
trackIds[i] = Long.valueOf(lastPathSegmentSplit[i]);
|
||||
trackIds[i] = Long.parseLong(uriTrackIds[i]);
|
||||
}
|
||||
return trackIds;
|
||||
}
|
||||
@@ -150,7 +151,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getTypeMime(@NonNull Uri uri) {
|
||||
private static String getTypeMime(@NonNull Uri uri) {
|
||||
return getTrackFileFormat(uri).getMimeType();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package de.dennisguse.opentracks.content.provider;
|
||||
|
||||
import android.location.LocationManager;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TrackPoint}.
|
||||
* An implementation can create new instances or reuse existing instances for optimization.
|
||||
*/
|
||||
public class TrackPointFactory {
|
||||
|
||||
/**
|
||||
* The default {@link TrackPointFactory} which creates a location each time.
|
||||
*/
|
||||
public static final TrackPointFactory DEFAULT_LOCATION_FACTORY = new TrackPointFactory();
|
||||
|
||||
public TrackPoint createLocation() {
|
||||
return new TrackPoint(LocationManager.GPS_PROVIDER);
|
||||
}
|
||||
}
|
||||
@@ -18,21 +18,15 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final long trackId;
|
||||
private final boolean descending;
|
||||
private final TrackPointFactory trackPointFactory;
|
||||
private final CachedTrackPointsIndexes indexes;
|
||||
private long lastTrackPointId = -1L;
|
||||
private Cursor cursor;
|
||||
|
||||
|
||||
public TrackPointIterator(ContentProviderUtils contentProviderUtils, long trackId, long startTrackPointId, boolean descending, TrackPointFactory trackPointFactory) {
|
||||
if (trackPointFactory == null) {
|
||||
throw new IllegalArgumentException("trackPointFactory is null");
|
||||
}
|
||||
|
||||
public TrackPointIterator(ContentProviderUtils contentProviderUtils, long trackId, long startTrackPointId, boolean descending) {
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
this.trackId = trackId;
|
||||
this.descending = descending;
|
||||
this.trackPointFactory = trackPointFactory;
|
||||
|
||||
cursor = getCursor(startTrackPointId);
|
||||
indexes = cursor != null ? new CachedTrackPointsIndexes(cursor)
|
||||
@@ -91,9 +85,7 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
}
|
||||
}
|
||||
lastTrackPointId = cursor.getLong(indexes.idIndex);
|
||||
TrackPoint trackPoint = trackPointFactory.createLocation();
|
||||
ContentProviderUtils.fillTrackPoint(cursor, indexes, trackPoint);
|
||||
return trackPoint;
|
||||
return ContentProviderUtils.fillTrackPoint(cursor, indexes);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,4 +100,13 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
if (cursor == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cursor.getCount();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -30,27 +29,25 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.TrackDataType;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* A fragment to display track chart to the user.
|
||||
* ChartFragment uses a {@link TrackStatisticsUpdater} internally and recomputes the {@link TrackStatistics} from the beginning.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
@@ -68,21 +65,19 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
return chartFragment;
|
||||
}
|
||||
|
||||
private final List<double[]> pendingPoints = new ArrayList<>();
|
||||
private final List<ChartPoint> pendingPoints = new ArrayList<>();
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
// Stats gathered from the received data
|
||||
private TripStatisticsUpdater tripStatisticsUpdater;
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||
private long startTime;
|
||||
|
||||
private boolean metricUnits = true;
|
||||
private boolean reportSpeed = true;
|
||||
//TODO Why is this needed?
|
||||
private int recordingDistanceInterval;
|
||||
|
||||
// Modes of operation
|
||||
private boolean chartByDistance;
|
||||
private final boolean[] chartShow = new boolean[]{true, true, true, true, true, true};
|
||||
|
||||
// UI elements
|
||||
private ChartView chartView;
|
||||
@@ -90,13 +85,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (!isResumed()) {
|
||||
return;
|
||||
}
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
|
||||
boolean metric = PreferencesUtils.isMetricUnits(getContext());
|
||||
if (metricUnits != metric) {
|
||||
metricUnits = metric;
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
if (metricUnits != chartView.getMetricUnits()) {
|
||||
chartView.setMetricUnits(metricUnits);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
@@ -109,12 +100,11 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
boolean speed = PreferencesUtils.isReportSpeed(getContext());
|
||||
if (reportSpeed != speed) {
|
||||
reportSpeed = speed;
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
|
||||
if (reportSpeed != chartView.getReportSpeed()) {
|
||||
chartView.setReportSpeed(reportSpeed);
|
||||
setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed);
|
||||
setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed);
|
||||
chartView.applyReportSpeed();
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -152,7 +142,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
chartByDistance = getArguments().getBoolean(KEY_CHART_VIEW_BY_DISTANCE_KEY, chartByDistance);
|
||||
chartByDistance = getArguments().getBoolean(KEY_CHART_VIEW_BY_DISTANCE_KEY, true);
|
||||
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceIntervalDefault(getContext());
|
||||
|
||||
@@ -178,6 +168,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
super.onResume();
|
||||
resumeTrackDataHub();
|
||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
|
||||
checkChartSettings();
|
||||
getActivity().runOnUiThread(updateChart);
|
||||
@@ -187,7 +178,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
pauseTrackDataHub();
|
||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -200,18 +191,18 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void onTrackUpdated(Track track) {
|
||||
if (isResumed()) {
|
||||
if (track == null || track.getTripStatistics() == null) {
|
||||
if (track == null || track.getTrackStatistics() == null) {
|
||||
startTime = -1L;
|
||||
return;
|
||||
}
|
||||
startTime = track.getTripStatistics().getStartTime();
|
||||
startTime = track.getTrackStatistics().getStartTime_ms();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
if (isResumed()) {
|
||||
tripStatisticsUpdater = startTime != -1L ? new TripStatisticsUpdater(startTime) : null;
|
||||
trackStatisticsUpdater = startTime != -1L ? new TrackStatisticsUpdater(startTime) : null;
|
||||
pendingPoints.clear();
|
||||
chartView.reset();
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -226,25 +217,25 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(Location location) {
|
||||
public void onSampledInTrackPoint(TrackPoint trackPoint) {
|
||||
if (isResumed()) {
|
||||
double[] data = new double[ChartView.NUM_SERIES + 1];
|
||||
fillDataPoint(location, data);
|
||||
pendingPoints.add(data);
|
||||
pendingPoints.add(createPendingPoint(trackPoint));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location location) {
|
||||
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
|
||||
if (isResumed()) {
|
||||
fillDataPoint(location, null);
|
||||
if (trackStatisticsUpdater != null) {
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone() {
|
||||
if (isResumed()) {
|
||||
chartView.addDataPoints(pendingPoints);
|
||||
chartView.addChartPoints(pendingPoints);
|
||||
pendingPoints.clear();
|
||||
runOnUiThread(updateChart);
|
||||
}
|
||||
@@ -275,46 +266,19 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
* Checks the chart settings.
|
||||
*/
|
||||
private void checkChartSettings() {
|
||||
boolean needUpdate = false;
|
||||
|
||||
if (setSeriesEnabled(ChartView.SPEED_SERIES, reportSpeed)) {
|
||||
needUpdate = true;
|
||||
}
|
||||
if (setSeriesEnabled(ChartView.PACE_SERIES, !reportSpeed)) {
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
boolean needUpdate = chartView.applyReportSpeed();
|
||||
if (needUpdate) {
|
||||
chartView.postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the series enabled value.
|
||||
*
|
||||
* @param index the series index
|
||||
* @param value the value
|
||||
* @return true if changed
|
||||
*/
|
||||
private boolean setSeriesEnabled(int index, boolean value) {
|
||||
if (chartShow[index] != value) {
|
||||
chartShow[index] = value;
|
||||
chartView.setChartValueSeriesEnabled(index, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the trackDataHub.
|
||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void resumeTrackDataHub() {
|
||||
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.TRACKS_TABLE,
|
||||
TrackDataType.WAYPOINTS_TABLE, TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE,
|
||||
TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
|
||||
trackDataHub.registerTrackDataListener(this, true, true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,16 +298,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the trackDataHub.
|
||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
||||
*/
|
||||
private synchronized void reloadTrackDataHub() {
|
||||
if (trackDataHub != null) {
|
||||
trackDataHub.reloadDataForListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a runnable on the UI thread if possible.
|
||||
*
|
||||
@@ -356,82 +310,15 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a location, fill in a data point, an array of double[]. <br>
|
||||
* data[0] = time/distance <br>
|
||||
* data[1] = elevation <br>
|
||||
* data[2] = speed <br>
|
||||
* data[3] = pace <br>
|
||||
* data[4] = heart rate <br>
|
||||
* data[5] = cadence <br>
|
||||
* data[6] = power <br>
|
||||
*
|
||||
* @param location the location
|
||||
* @param data the data point to fill in, can be null
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void fillDataPoint(Location location, double[] data) {
|
||||
double timeOrDistance = Double.NaN;
|
||||
double elevation = Double.NaN;
|
||||
double speed = Double.NaN;
|
||||
double pace = Double.NaN;
|
||||
|
||||
if (tripStatisticsUpdater != null) {
|
||||
tripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
|
||||
if (chartByDistance) {
|
||||
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
timeOrDistance = distance;
|
||||
} else {
|
||||
timeOrDistance = tripStatistics.getTotalTime();
|
||||
}
|
||||
|
||||
elevation = tripStatisticsUpdater.getSmoothedElevation();
|
||||
if (!metricUnits) {
|
||||
elevation *= UnitConversions.M_TO_FT;
|
||||
}
|
||||
|
||||
speed = tripStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH;
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
pace = speed == 0 ? 0.0 : 60.0 / speed;
|
||||
}
|
||||
|
||||
double heartRate = Double.NaN;
|
||||
double cadence = Double.NaN;
|
||||
double power = Double.NaN;
|
||||
if (location instanceof TrackPoint && ((TrackPoint) location).getSensorDataSet() != null) {
|
||||
SensorDataSet sensorDataSet = ((TrackPoint) location).getSensorDataSet();
|
||||
if (sensorDataSet.hasHeartRate()) {
|
||||
heartRate = sensorDataSet.getHeartRate();
|
||||
}
|
||||
if (sensorDataSet.hasCadence()) {
|
||||
cadence = sensorDataSet.getCadence();
|
||||
}
|
||||
if (sensorDataSet.hasPower()) {
|
||||
power = sensorDataSet.getPower();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Is related to ChartView.ELEVATION_SERIES etc.
|
||||
if (data != null) {
|
||||
data[0] = timeOrDistance;
|
||||
data[1] = elevation;
|
||||
data[2] = speed;
|
||||
data[3] = pace;
|
||||
data[4] = heartRate;
|
||||
data[5] = cadence;
|
||||
data[6] = power;
|
||||
}
|
||||
ChartPoint createPendingPoint(@NonNull TrackPoint trackPoint) {
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
return new ChartPoint(trackStatisticsUpdater, trackPoint.getSensorDataSet(), chartByDistance, chartView.getMetricUnits());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setTripStatisticsUpdater(long time) {
|
||||
tripStatisticsUpdater = new TripStatisticsUpdater(time);
|
||||
void setTrackStatisticsUpdater(long time) {
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater(time);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -441,12 +328,12 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
@VisibleForTesting
|
||||
void setMetricUnits(boolean value) {
|
||||
metricUnits = value;
|
||||
chartView.setMetricUnits(value);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setReportSpeed(boolean value) {
|
||||
reportSpeed = value;
|
||||
chartView.setReportSpeed(value);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -32,7 +32,7 @@ import de.dennisguse.opentracks.util.ResourceUtils;
|
||||
*
|
||||
* @author apoorvn
|
||||
*/
|
||||
public class ChooseActivityTypeImageAdapter extends BaseAdapter {
|
||||
class ChooseActivityTypeImageAdapter extends BaseAdapter {
|
||||
|
||||
private final List<Integer> imageIds;
|
||||
private int selected = -1;
|
||||
|
||||
@@ -60,6 +60,8 @@ public class MarkerDetailFragment extends Fragment {
|
||||
|
||||
private static final long HIDE_TEXT_DELAY = 4 * UnitConversions.ONE_SECOND_MS;
|
||||
|
||||
private MenuItem shareMarkerImageMenuItem;
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private Handler handler;
|
||||
private ImageView photoView;
|
||||
@@ -145,6 +147,7 @@ public class MarkerDetailFragment extends Fragment {
|
||||
// Need to update the waypoint in case returning after an edit
|
||||
updateWaypoint(true);
|
||||
updateUi();
|
||||
updateMenuItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,8 +173,14 @@ public class MarkerDetailFragment extends Fragment {
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.marker_detail, menu);
|
||||
|
||||
shareMarkerImageMenuItem = menu.findItem(R.id.marker_detail_share);
|
||||
updateWaypoint(false);
|
||||
updateMenuItems();
|
||||
}
|
||||
|
||||
private void updateMenuItems() {
|
||||
if (shareMarkerImageMenuItem != null)
|
||||
shareMarkerImageMenuItem.setVisible(waypoint.hasPhoto());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,6 +197,13 @@ public class MarkerDetailFragment extends Fragment {
|
||||
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.marker_detail_share:
|
||||
if (waypoint.hasPhoto()) {
|
||||
intent = IntentUtils.newShareImageIntent(getContext(), waypoint.getPhotoURI());
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
}
|
||||
return true;
|
||||
case R.id.marker_detail_delete:
|
||||
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), new long[]{markerId});
|
||||
return true;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
@@ -34,20 +33,18 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.TrackDataType;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
@@ -72,8 +69,8 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
//TODO Initialize immediately and remove in onDestroy()
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
|
||||
private Location lastLocation = null;
|
||||
private TripStatistics lastTripStatistics = null;
|
||||
private TrackPoint lastTrackPoint = null;
|
||||
private TrackStatistics lastTrackStatistics = null;
|
||||
|
||||
private String category = "";
|
||||
@Deprecated //TODO This should be handled somewhere else; not in the UI.
|
||||
@@ -96,6 +93,16 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.recording_track_id_key, key)) {
|
||||
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(getContext());
|
||||
if (PreferencesUtils.getRecordingTrackId(getContext()) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
|
||||
// A recording track id has been set -> Resumes track and starts timer.
|
||||
resumeTrackDataHub();
|
||||
if (trackRecordingServiceConnection == null) {
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
|
||||
}
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
|
||||
handlerUpdateUI.post(updateUIeachSecond);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -114,7 +121,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
private final Runnable updateUIeachSecond = new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed() && isSelectedTrackRecording()) {
|
||||
if (!isSelectedTrackPaused() && lastTripStatistics != null) {
|
||||
if (!isSelectedTrackPaused() && lastTrackStatistics != null) {
|
||||
updateTotalTime();
|
||||
updateSensorDataUI();
|
||||
}
|
||||
@@ -137,17 +144,12 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
private TextView speedMovingLabel;
|
||||
private TextView speedMovingValue;
|
||||
private TextView speedMovingUnit;
|
||||
private View gradeElevationSeparator;
|
||||
private View gradeElevationContainer;
|
||||
private TextView elevationMinValue;
|
||||
private TextView elevationMinUnit;
|
||||
private TextView elevationMaxValue;
|
||||
private TextView elevationMaxUnit;
|
||||
private View elevationSeparator;
|
||||
private View elevationContainer;
|
||||
private View speedContainer;
|
||||
private TextView speedLabel;
|
||||
private TextView speedValue;
|
||||
private TextView speedUnit;
|
||||
private View elevationContainer;
|
||||
private TextView elevationValue;
|
||||
private TextView elevationUnit;
|
||||
private View coordinateSeparator;
|
||||
@@ -185,21 +187,14 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||
|
||||
gradeElevationSeparator = view.findViewById(R.id.stats_elevation_separator);
|
||||
gradeElevationContainer = view.findViewById(R.id.stats_elevation_container);
|
||||
|
||||
elevationMinValue = view.findViewById(R.id.stats_elevation_min_value);
|
||||
elevationMinUnit = view.findViewById(R.id.stats_elevation_min_unit);
|
||||
|
||||
elevationMaxValue = view.findViewById(R.id.stats_elevation_max_value);
|
||||
elevationMaxUnit = view.findViewById(R.id.stats_elevation_max_unit);
|
||||
elevationSeparator = view.findViewById(R.id.stats_elevation_separator);
|
||||
elevationContainer = view.findViewById(R.id.stats_elevation_container);
|
||||
|
||||
speedContainer = view.findViewById(R.id.stats_speed);
|
||||
speedLabel = view.findViewById(R.id.stats_speed_label);
|
||||
speedValue = view.findViewById(R.id.stats_speed_value);
|
||||
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
||||
|
||||
elevationContainer = view.findViewById(R.id.stats_elevation);
|
||||
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
|
||||
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
||||
|
||||
@@ -246,7 +241,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
resumeTrackDataHub();
|
||||
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(getContext(), null);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
|
||||
handlerUpdateUI.post(updateUIeachSecond);
|
||||
@@ -299,21 +294,14 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
speedMovingValue = null;
|
||||
speedMovingUnit = null;
|
||||
|
||||
gradeElevationSeparator = null;
|
||||
gradeElevationContainer = null;
|
||||
|
||||
elevationMinValue = null;
|
||||
elevationMinUnit = null;
|
||||
|
||||
elevationMaxValue = null;
|
||||
elevationMaxUnit = null;
|
||||
elevationSeparator = null;
|
||||
elevationContainer = null;
|
||||
|
||||
speedContainer = null;
|
||||
speedLabel = null;
|
||||
speedValue = null;
|
||||
speedUnit = null;
|
||||
|
||||
elevationContainer = null;
|
||||
elevationValue = null;
|
||||
elevationUnit = null;
|
||||
|
||||
@@ -331,7 +319,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
lastTripStatistics = track != null ? track.getTripStatistics() : null;
|
||||
lastTrackStatistics = track != null ? track.getTrackStatistics() : null;
|
||||
category = track != null ? track.getCategory() : "";
|
||||
updateUI();
|
||||
}
|
||||
@@ -342,17 +330,17 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
lastLocation = null;
|
||||
lastTrackPoint = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(Location location) {
|
||||
lastLocation = location;
|
||||
public void onSampledInTrackPoint(TrackPoint trackPoint) {
|
||||
lastTrackPoint = trackPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(Location location) {
|
||||
lastLocation = location;
|
||||
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
|
||||
lastTrackPoint = trackPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -363,15 +351,15 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
public void run() {
|
||||
if (isResumed()) {
|
||||
if (!isSelectedTrackRecording() || isSelectedTrackPaused()) {
|
||||
lastLocation = null;
|
||||
lastTrackPoint = null;
|
||||
}
|
||||
|
||||
if (lastLocation != null) {
|
||||
boolean hasFix = !LocationUtils.isLocationOld(lastLocation);
|
||||
boolean hasGoodFix = lastLocation.hasAccuracy() && lastLocation.getAccuracy() < recordingGpsAccuracy;
|
||||
if (lastTrackPoint != null) {
|
||||
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation());
|
||||
boolean hasGoodFix = lastTrackPoint.hasAccuracy() && lastTrackPoint.getAccuracy() < recordingGpsAccuracy;
|
||||
|
||||
if (!hasFix || !hasGoodFix) {
|
||||
lastLocation = null;
|
||||
lastTrackPoint = null;
|
||||
}
|
||||
}
|
||||
setLocationValues();
|
||||
@@ -402,8 +390,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
*/
|
||||
private synchronized void resumeTrackDataHub() {
|
||||
trackDataHub = ((TrackDetailActivity) getActivity()).getTrackDataHub();
|
||||
trackDataHub.registerTrackDataListener(this, EnumSet.of(TrackDataType.TRACKS_TABLE,
|
||||
TrackDataType.SAMPLED_IN_TRACK_POINTS_TABLE, TrackDataType.SAMPLED_OUT_TRACK_POINTS_TABLE));
|
||||
trackDataHub.registerTrackDataListener(this, true, false, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -486,7 +473,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
// Set total distance
|
||||
{
|
||||
double totalDistance = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getTotalDistance();
|
||||
double totalDistance = lastTrackStatistics == null ? Double.NaN : lastTrackStatistics.getTotalDistance();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, metricUnits);
|
||||
|
||||
distanceValue.setText(parts.first);
|
||||
@@ -505,14 +492,14 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
// Set time
|
||||
if (lastTripStatistics != null) {
|
||||
movingTimeValue.setText(StringUtils.formatElapsedTime(lastTripStatistics.getMovingTime()));
|
||||
if (lastTrackStatistics != null) {
|
||||
movingTimeValue.setText(StringUtils.formatElapsedTime(lastTrackStatistics.getMovingTime()));
|
||||
updateTotalTime();
|
||||
}
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = lastTripStatistics != null ? lastTripStatistics.getAverageSpeed() : Double.NaN;
|
||||
double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageSpeed() : Double.NaN;
|
||||
speedAvgLabel.setText(reportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
@@ -522,7 +509,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
double speed = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxSpeed();
|
||||
double speed = lastTrackStatistics == null ? Double.NaN : lastTrackStatistics.getMaxSpeed();
|
||||
|
||||
speedMaxLabel.setText(reportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
@@ -533,7 +520,7 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
double speed = lastTripStatistics != null ? lastTripStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||
double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||
|
||||
speedMovingLabel.setText(reportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
@@ -542,44 +529,36 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
speedMovingUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
|
||||
// Set elevation
|
||||
// Make elevation visible?
|
||||
{
|
||||
boolean showElevation = PreferencesUtils.isShowStatsGradeElevation(getContext());
|
||||
gradeElevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
gradeElevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showElevation) {
|
||||
{
|
||||
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMinElevation();
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
|
||||
|
||||
elevationMinValue.setText(parts.first);
|
||||
elevationMinUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
{
|
||||
double elevation = lastTripStatistics == null ? Double.NaN : lastTripStatistics.getMaxElevation();
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevation, metricUnits);
|
||||
|
||||
elevationMaxValue.setText(parts.first);
|
||||
elevationMaxUnit.setText(parts.second);
|
||||
}
|
||||
}
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||
elevationSeparator.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTotalTime() {
|
||||
long totalTime;
|
||||
if (isSelectedTrackRecording()) {
|
||||
totalTime = System.currentTimeMillis() - lastTripStatistics.getStopTime() + lastTripStatistics.getTotalTime();
|
||||
totalTime = calculateTotalTime();
|
||||
} else {
|
||||
totalTime = lastTripStatistics.getTotalTime();
|
||||
totalTime = lastTrackStatistics.getTotalTime();
|
||||
}
|
||||
totalTimeValueView.setText(StringUtils.formatElapsedTime(totalTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return time from service.
|
||||
* If service isn't bound then use lastTrackStatistics for calculate it.
|
||||
*/
|
||||
private long calculateTotalTime() {
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
return trackRecordingService.getTotalTime();
|
||||
} else {
|
||||
return System.currentTimeMillis() - lastTrackStatistics.getStopTime_ms() + lastTrackStatistics.getTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocationValues() {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(getContext());
|
||||
@@ -590,18 +569,18 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
if (isRecording) {
|
||||
speedLabel.setText(reportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
double speed = lastLocation != null && lastLocation.hasSpeed() ? lastLocation.getSpeed() : Double.NaN;
|
||||
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, metricUnits, reportSpeed);
|
||||
speedValue.setText(parts.first);
|
||||
speedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set elevation
|
||||
boolean showGradeElevation = isRecording && PreferencesUtils.isShowStatsGradeElevation(getContext());
|
||||
elevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
|
||||
boolean showElevation = isRecording && PreferencesUtils.isShowStatsElevation(getContext());
|
||||
elevationContainer.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showGradeElevation) {
|
||||
double altitude = lastLocation != null && lastLocation.hasAltitude() ? lastLocation.getAltitude() : Double.NaN;
|
||||
if (showElevation) {
|
||||
double altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? lastTrackPoint.getAltitude() : Double.NaN;
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, metricUnits);
|
||||
|
||||
elevationValue.setText(parts.first);
|
||||
@@ -614,11 +593,11 @@ public class StatsFragment extends Fragment implements TrackDataListener {
|
||||
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
|
||||
if (showCoordinate) {
|
||||
double latitude = lastLocation != null ? lastLocation.getLatitude() : Double.NaN;
|
||||
double latitude = lastTrackPoint != null ? lastTrackPoint.getLatitude() : Double.NaN;
|
||||
String latitudeText = Double.isNaN(latitude) || Double.isInfinite(latitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(latitude);
|
||||
latitudeValue.setText(latitudeText);
|
||||
|
||||
double longitude = lastLocation != null ? lastLocation.getLongitude() : Double.NaN;
|
||||
double longitude = lastTrackPoint != null ? lastTrackPoint.getLongitude() : Double.NaN;
|
||||
String longitudeText = Double.isNaN(longitude) || Double.isInfinite(longitude) ? getContext().getString(R.string.value_unknown) : StringUtils.formatCoordinate(longitude);
|
||||
longitudeValue.setText(longitudeText);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import de.dennisguse.opentracks.util.SystemUtils;
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
//TODO Make independent from ExportActivity?
|
||||
public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private static final String TAG = ExportAsyncTask.class.getSimpleName();
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
@@ -70,18 +70,6 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current activity associated with this AsyncTask.
|
||||
*
|
||||
* @param exportActivity the current activity, can be null
|
||||
*/
|
||||
public void setActivity(ExportActivity exportActivity) {
|
||||
this.exportActivity = exportActivity;
|
||||
if (completed && exportActivity != null) {
|
||||
exportActivity.onAsyncTaskCompleted(processedTrackCount, totalTrackCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (exportActivity != null) {
|
||||
|
||||
@@ -18,7 +18,6 @@ package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -29,7 +28,6 @@ import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointFactory;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
|
||||
@@ -72,9 +70,9 @@ public class FileTrackExporter implements TrackExporter {
|
||||
writeWaypoints(track1);
|
||||
}
|
||||
trackWriter.writeBeginTracks();
|
||||
long startTime = tracks[0].getTripStatistics().getStartTime();
|
||||
long startTime = tracks[0].getTrackStatistics().getStartTime_ms();
|
||||
for (Track track : tracks) {
|
||||
long offset = track.getTripStatistics().getStartTime() - startTime;
|
||||
long offset = track.getTrackStatistics().getStartTime_ms() - startTime;
|
||||
writeLocations(track, offset);
|
||||
}
|
||||
trackWriter.writeEndTracks();
|
||||
@@ -126,25 +124,25 @@ public class FileTrackExporter implements TrackExporter {
|
||||
boolean wroteTrack = false;
|
||||
boolean wroteSegment = false;
|
||||
boolean isLastLocationValid = false;
|
||||
TrackWriterTrackPointFactory trackPointFactory = new TrackWriterTrackPointFactory();
|
||||
int locationNumber = 0;
|
||||
TrackPoint lastTrackPoint = null;
|
||||
|
||||
try (TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, trackPointFactory)) {
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
|
||||
|
||||
while (locationIterator.hasNext()) {
|
||||
while (trackPointIterator.hasNext()) {
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
Location location = locationIterator.next();
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
|
||||
setLocationTime(location, offset);
|
||||
setLocationTime(trackPoint, offset);
|
||||
locationNumber++;
|
||||
|
||||
boolean isLocationValid = LocationUtils.isValidLocation(location);
|
||||
boolean isLocationValid = LocationUtils.isValidLocation(trackPoint.getLocation());
|
||||
boolean isSegmentValid = isLocationValid && isLastLocationValid;
|
||||
if (!wroteTrack && isSegmentValid) {
|
||||
// Found the first two consecutive locations that are valid
|
||||
trackWriter.writeBeginTrack(track, trackPointFactory.lastLocation);
|
||||
trackWriter.writeBeginTrack(track, lastTrackPoint);
|
||||
wroteTrack = true;
|
||||
}
|
||||
|
||||
@@ -154,14 +152,14 @@ public class FileTrackExporter implements TrackExporter {
|
||||
trackWriter.writeOpenSegment();
|
||||
wroteSegment = true;
|
||||
|
||||
// Write the previous location, which we had previously skipped
|
||||
trackWriter.writeLocation(trackPointFactory.lastLocation);
|
||||
// Write the previous trackPoint, which we had previously skipped
|
||||
trackWriter.writeTrackPoint(lastTrackPoint);
|
||||
}
|
||||
|
||||
// Write the current location
|
||||
trackWriter.writeLocation(location);
|
||||
// Write the current trackPoint
|
||||
trackWriter.writeTrackPoint(trackPoint);
|
||||
if (trackExporterListener != null) {
|
||||
trackExporterListener.onProgressUpdate(locationNumber, track.getNumberOfPoints());
|
||||
trackExporterListener.onProgressUpdate(locationNumber, trackPointIterator.getCount());
|
||||
}
|
||||
} else {
|
||||
if (wroteSegment) {
|
||||
@@ -169,7 +167,7 @@ public class FileTrackExporter implements TrackExporter {
|
||||
wroteSegment = false;
|
||||
}
|
||||
}
|
||||
trackPointFactory.swapLocations();
|
||||
lastTrackPoint = trackPoint;
|
||||
isLastLocationValid = isLocationValid;
|
||||
}
|
||||
|
||||
@@ -179,7 +177,7 @@ public class FileTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
if (wroteTrack) {
|
||||
Location lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId());
|
||||
TrackPoint lastValidTrackPoint = contentProviderUtils.getLastValidTrackPoint(track.getId());
|
||||
setLocationTime(lastValidTrackPoint, offset);
|
||||
trackWriter.writeEndTrack(track, lastValidTrackPoint);
|
||||
} else {
|
||||
@@ -191,41 +189,14 @@ public class FileTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a location time.
|
||||
* Sets a trackPoint time.
|
||||
*
|
||||
* @param location the location
|
||||
* @param offset the time offset
|
||||
* @param trackPoint the trackPoint
|
||||
* @param offset the time offset
|
||||
*/
|
||||
private void setLocationTime(Location location, long offset) {
|
||||
if (location != null) {
|
||||
location.setTime(location.getTime() - offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track writer location factory. Keeping the last two locations.
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
private class TrackWriterTrackPointFactory extends TrackPointFactory {
|
||||
TrackPoint currentLocation;
|
||||
TrackPoint lastLocation;
|
||||
|
||||
@Override
|
||||
public TrackPoint createLocation() {
|
||||
if (currentLocation == null) {
|
||||
currentLocation = new TrackPoint("");
|
||||
}
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
void swapLocations() {
|
||||
TrackPoint tempLocation = lastLocation;
|
||||
lastLocation = currentLocation;
|
||||
currentLocation = tempLocation;
|
||||
if (currentLocation != null) {
|
||||
currentLocation.reset();
|
||||
}
|
||||
private void setLocationTime(TrackPoint trackPoint, long offset) {
|
||||
if (trackPoint != null) {
|
||||
trackPoint.setTime(trackPoint.getTime() - offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
@@ -139,7 +140,7 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginTrack(Track track, Location startLocation) {
|
||||
public void writeBeginTrack(Track track, TrackPoint startTrackPoint) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<trk>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
@@ -150,7 +151,7 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTrack(Track track, Location endLocation) {
|
||||
public void writeEndTrack(Track track, TrackPoint endTrackPoint) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</trk>");
|
||||
}
|
||||
@@ -167,14 +168,14 @@ public class GpxTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLocation(Location location) {
|
||||
public void writeTrackPoint(TrackPoint trackPoint) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<trkpt " + formatLocation(location) + ">");
|
||||
if (location.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
|
||||
printWriter.println("<trkpt " + formatLocation(trackPoint.getLocation()) + ">");
|
||||
if (trackPoint.hasAltitude()) {
|
||||
printWriter.println("<ele>" + ELEVATION_FORMAT.format(trackPoint.getAltitude()) + "</ele>");
|
||||
}
|
||||
printWriter.println(
|
||||
"<time>" + StringUtils.formatDateTimeIso8601(location.getTime()) + "</time>");
|
||||
"<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||
printWriter.println("</trkpt>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,12 +69,12 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
private final List<Float> cadenceList = new ArrayList<>();
|
||||
private final List<Float> heartRateList = new ArrayList<>();
|
||||
|
||||
private Location startLocation;
|
||||
private TrackPoint startTrackPoint;
|
||||
|
||||
/**
|
||||
* @param context the context
|
||||
* @param hasMultipleTracks should encode multiple tracks into one file?
|
||||
* @param exportTrackDetail should detailed information about the track be exported (e.g., title, description, waypoints, timing)?
|
||||
* @param exportTrackDetail should detailed information about the track be exported (e.g., title, description, waypoints, timing)?
|
||||
* @param exportSensorData should {@link SensorDataSet} be exported?
|
||||
* @param exportPhotos should pictures be exported (if true: exports to KMZ)?
|
||||
*/
|
||||
@@ -188,11 +188,12 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBeginTrack(Track track, Location startLocation) {
|
||||
this.startLocation = startLocation;
|
||||
public void writeBeginTrack(Track track, TrackPoint startTrackPoint) {
|
||||
this.startTrackPoint = startTrackPoint;
|
||||
if (printWriter != null) {
|
||||
String name = context.getString(R.string.marker_label_start, track.getName());
|
||||
writePlacemark(name, "", "", START_STYLE, startLocation);
|
||||
Location location = startTrackPoint != null ? startTrackPoint.getLocation() : null;
|
||||
writePlacemark(name, "", "", START_STYLE, location);
|
||||
printWriter.println("<Placemark>");
|
||||
|
||||
if (exportTrackDetail) {
|
||||
@@ -210,7 +211,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndTrack(Track track, Location endLocation) {
|
||||
public void writeEndTrack(Track track, TrackPoint endTrackPoint) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("</gx:MultiTrack>");
|
||||
printWriter.println("</Placemark>");
|
||||
@@ -218,7 +219,8 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
if (exportTrackDetail) {
|
||||
String name = context.getString(R.string.marker_label_end, track.getName());
|
||||
String description = descriptionGenerator.generateTrackDescription(track, false);
|
||||
writePlacemark(name, "", description, END_STYLE, endLocation);
|
||||
Location location = endTrackPoint != null ? endTrackPoint.getLocation() : null;
|
||||
writePlacemark(name, "", description, END_STYLE, location);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,16 +258,16 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLocation(Location location) {
|
||||
public void writeTrackPoint(TrackPoint trackPoint) {
|
||||
if (printWriter != null) {
|
||||
if (exportTrackDetail) {
|
||||
printWriter.println("<when>" + getTime(location) + "</when>");
|
||||
printWriter.println("<when>" + getTime(trackPoint.getLocation()) + "</when>");
|
||||
}
|
||||
|
||||
printWriter.println("<gx:coord>" + getCoordinates(location, " ") + "</gx:coord>");
|
||||
printWriter.println("<gx:coord>" + getCoordinates(trackPoint.getLocation(), " ") + "</gx:coord>");
|
||||
|
||||
if (exportSensorData && location instanceof TrackPoint) {
|
||||
SensorDataSet sensorDataSet = ((TrackPoint) location).getSensorDataSet();
|
||||
if (exportSensorData) {
|
||||
SensorDataSet sensorDataSet = trackPoint.getSensorDataSet();
|
||||
if (sensorDataSet != null) {
|
||||
if (sensorDataSet.hasHeartRate()) {
|
||||
heartRateList.add(sensorDataSet.getHeartRate());
|
||||
@@ -345,7 +347,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
writeCategory(category);
|
||||
|
||||
if (exportPhotos) {
|
||||
printWriter.println("<Icon><href>" + Uri.decode(photoUrl) + "</href></Icon>");
|
||||
printWriter.println("<Icon><href>" + KmzTrackExporter.buildKmzImageFilePath(Uri.parse(photoUrl)) + "</href></Icon>");
|
||||
}
|
||||
|
||||
printWriter.print("<ViewVolume>");
|
||||
@@ -371,7 +373,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
if (exportTrackDetail) {
|
||||
return StringUtils.formatDateTimeIso8601(location.getTime());
|
||||
} else {
|
||||
return StringUtils.formatDateTimeIso8601(location.getTime() - startLocation.getTime());
|
||||
return StringUtils.formatDateTimeIso8601(location.getTime() - startTrackPoint.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +388,7 @@ public class KmlTrackWriter implements TrackWriter {
|
||||
if (trackPointId == -1L) {
|
||||
return location.getBearing();
|
||||
}
|
||||
Location viewLocation;
|
||||
TrackPoint viewLocation;
|
||||
try (Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackPointId, 10, true)) {
|
||||
if (cursor == null || cursor.getCount() == 0) {
|
||||
return location.getBearing();
|
||||
|
||||
@@ -126,7 +126,7 @@ public class KmzTrackExporter implements TrackExporter {
|
||||
Uri uri = Uri.parse(photoUrl);
|
||||
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
|
||||
ZipEntry zipEntry = new ZipEntry(KMZ_IMAGES_DIR + File.separatorChar + FileUtils.sanitizeFileName(uri.getLastPathSegment()));
|
||||
ZipEntry zipEntry = new ZipEntry(buildKmzImageFilePath(uri));
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
|
||||
if (inputStream == null) throw new FileNotFoundException();
|
||||
@@ -147,4 +147,13 @@ public class KmzTrackExporter implements TrackExporter {
|
||||
outputStream.write(buffer, 0, byteCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the path for the file that will be saved inside KMZ_IMAGES_DIR.
|
||||
*
|
||||
* @param uri URI object.
|
||||
*/
|
||||
public static String buildKmzImageFilePath(Uri uri) {
|
||||
return KMZ_IMAGES_DIR + File.separatorChar + FileUtils.sanitizeFileName(uri.getLastPathSegment());
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ public interface TrackExporterListener {
|
||||
/**
|
||||
* Called to update progress.
|
||||
*
|
||||
* @param number the number of locations written
|
||||
* @param max the maximum number of locations in a track, for calculation of
|
||||
* @param number the number of {@link de.dennisguse.opentracks.content.data.TrackPoint}s written
|
||||
* @param max the maximum number of {@link de.dennisguse.opentracks.content.data.TrackPoint}s in a {@link de.dennisguse.opentracks.content.data.Track}, for calculation of
|
||||
* completion percentage
|
||||
*/
|
||||
void onProgressUpdate(int number, int max);
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
|
||||
/**
|
||||
@@ -35,13 +34,13 @@ import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
* {@link #writeEndWaypoints()}
|
||||
* {@link #writeBeginTracks()}
|
||||
* For each track:
|
||||
* {@link #writeBeginTrack(Track, Location)}
|
||||
* {@link #writeBeginTrack(Track, TrackPoint)}
|
||||
* For each segment:
|
||||
* {@link #writeOpenSegment()}
|
||||
* For each location in the segment:
|
||||
* {@link #writeLocation(Location)}
|
||||
* For each trackPoint in the segment:
|
||||
* {@link #writeTrackPoint(TrackPoint)}
|
||||
* {@link #writeCloseSegment()}
|
||||
* {@link #writeEndTrack(Track, Location)}
|
||||
* {@link #writeEndTrack(Track, TrackPoint)}
|
||||
* {@link #writeEndTracks()}
|
||||
* {@link #writeFooter()}
|
||||
* {@link #close()}
|
||||
@@ -107,18 +106,18 @@ public interface TrackWriter {
|
||||
/**
|
||||
* Writes the beginning of a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @param startLocation the start location
|
||||
* @param track the track
|
||||
* @param startTrackPoint the start location
|
||||
*/
|
||||
void writeBeginTrack(Track track, Location startLocation);
|
||||
void writeBeginTrack(Track track, TrackPoint startTrackPoint);
|
||||
|
||||
/**
|
||||
* Writes the end of a track.
|
||||
*
|
||||
* @param track the track
|
||||
* @param endLocation the end location
|
||||
* @param track the track
|
||||
* @param endTrackPoint the end location
|
||||
*/
|
||||
void writeEndTrack(Track track, Location endLocation);
|
||||
void writeEndTrack(Track track, TrackPoint endTrackPoint);
|
||||
|
||||
/**
|
||||
* Writes open segment.
|
||||
@@ -131,9 +130,9 @@ public interface TrackWriter {
|
||||
void writeCloseSegment();
|
||||
|
||||
/**
|
||||
* Writes a location.
|
||||
* Writes a trackPoint.
|
||||
*
|
||||
* @param location the location
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void writeLocation(Location location);
|
||||
void writeTrackPoint(TrackPoint trackPoint);
|
||||
}
|
||||
@@ -17,8 +17,6 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -39,12 +37,11 @@ import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointFactory;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
@@ -158,12 +155,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
|
||||
int waypointPosition = -1;
|
||||
Waypoint waypoint = null;
|
||||
Location location = null;
|
||||
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
||||
@Deprecated // TODO Should not be necessary anymore?
|
||||
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime());
|
||||
TrackPoint trackPoint = null;
|
||||
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
|
||||
// TODO Should not be necessary anymore?
|
||||
TrackStatisticsUpdater markerTrackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
|
||||
|
||||
try (TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
|
||||
|
||||
while (true) {
|
||||
if (waypoint == null) {
|
||||
@@ -175,38 +172,38 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
}
|
||||
}
|
||||
|
||||
if (location == null) {
|
||||
if (!locationIterator.hasNext()) {
|
||||
if (trackPoint == null) {
|
||||
if (!trackPointIterator.hasNext()) {
|
||||
// No more track points. Ignore the rest of the waypoints.
|
||||
return;
|
||||
}
|
||||
location = locationIterator.next();
|
||||
trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval);
|
||||
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
trackPoint = trackPointIterator.next();
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
markerTrackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
}
|
||||
|
||||
if (waypoint.getLocation().getTime() > location.getTime()) {
|
||||
location = null;
|
||||
} else if (waypoint.getLocation().getTime() < location.getTime()) {
|
||||
Log.w(TAG, "Ignoring waypoint: current location was after waypoint.");
|
||||
if (waypoint.getLocation().getTime() > trackPoint.getTime()) {
|
||||
trackPoint = null;
|
||||
} else if (waypoint.getLocation().getTime() < trackPoint.getTime()) {
|
||||
Log.w(TAG, "Ignoring waypoint: current trackPoint was after waypoint.");
|
||||
waypoint = null;
|
||||
} else {
|
||||
// The waypoint location time matches the track point time
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
// Invalid location, load the next location
|
||||
location = null;
|
||||
// The waypoint trackPoint time matches the track point time
|
||||
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
|
||||
// Invalid trackPoint, load the next trackPoint
|
||||
trackPoint = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Valid location
|
||||
if (location.getLatitude() == waypoint.getLocation().getLatitude() && location.getLongitude() == waypoint.getLocation().getLongitude()) {
|
||||
// Valid trackPoint
|
||||
if (trackPoint.getLatitude() == waypoint.getLocation().getLatitude() && trackPoint.getLongitude() == waypoint.getLocation().getLongitude()) {
|
||||
String waypointDescription = waypoint.getDescription();
|
||||
String icon = context.getString(R.string.marker_waypoint_icon_url);
|
||||
double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance();
|
||||
long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime();
|
||||
double length = trackStatisticsUpdater.getTrackStatistics().getTotalDistance();
|
||||
long duration = trackStatisticsUpdater.getTrackStatistics().getTotalTime();
|
||||
|
||||
// Insert waypoint
|
||||
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, waypoint.getCategory(), icon, track.getId(), length, duration, location, waypoint.getPhotoUrl());
|
||||
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, waypoint.getCategory(), icon, track.getId(), length, duration, trackPoint.getLocation(), waypoint.getPhotoUrl());
|
||||
contentProviderUtils.insertWaypoint(newWaypoint);
|
||||
}
|
||||
|
||||
@@ -257,12 +254,11 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
if (icon != null) {
|
||||
trackData.track.setIcon(icon);
|
||||
}
|
||||
if (trackData.tripStatisticsUpdater == null) {
|
||||
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(trackData.importTime);
|
||||
trackData.tripStatisticsUpdater.updateTime(trackData.importTime);
|
||||
if (trackData.trackStatisticsUpdater == null) {
|
||||
trackData.trackStatisticsUpdater = new TrackStatisticsUpdater(trackData.importTime);
|
||||
trackData.trackStatisticsUpdater.updateTime(trackData.importTime);
|
||||
}
|
||||
trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics());
|
||||
trackData.track.setNumberOfPoints(trackData.numberOfLocations);
|
||||
trackData.track.setTrackStatistics(trackData.trackStatisticsUpdater.getTrackStatistics());
|
||||
contentProviderUtils.updateTrack(trackData.track);
|
||||
}
|
||||
|
||||
@@ -274,7 +270,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
|
||||
//If not the first segment, add a pause separator if there is at least one location in the last segment.
|
||||
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) {
|
||||
insertLocation(createLocation(TrackPointsColumns.PAUSE_LATITUDE, 0.0, 0.0, trackData.lastLocationInCurrentSegment.getTime()));
|
||||
insertLocation(TrackPoint.createPauseWithTime(trackData.lastLocationInCurrentSegment.getTime()));
|
||||
}
|
||||
trackData.lastLocationInCurrentSegment = null;
|
||||
}
|
||||
@@ -288,13 +284,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
return;
|
||||
}
|
||||
|
||||
Waypoint waypoint = new Waypoint();
|
||||
Location location = createLocation();
|
||||
TrackPoint trackPoint = createTrackPoint();
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
|
||||
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
|
||||
throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint));
|
||||
}
|
||||
waypoint.setLocation(location);
|
||||
Waypoint waypoint = new Waypoint(trackPoint.getLocation());
|
||||
|
||||
if (name != null) {
|
||||
waypoint.setName(name);
|
||||
@@ -315,12 +310,12 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
/**
|
||||
* Gets a track point.
|
||||
*/
|
||||
protected Location getTrackPoint() throws SAXException {
|
||||
Location location = createLocation();
|
||||
protected TrackPoint getTrackPoint() throws SAXException {
|
||||
TrackPoint trackPoint = createTrackPoint();
|
||||
|
||||
// Calculate derived attributes from the previous point
|
||||
if (trackData.lastLocationInCurrentSegment != null && trackData.lastLocationInCurrentSegment.getTime() != 0) {
|
||||
long timeDifference = location.getTime() - trackData.lastLocationInCurrentSegment.getTime();
|
||||
long timeDifference = trackPoint.getTime() - trackData.lastLocationInCurrentSegment.getTime();
|
||||
|
||||
// Check for negative time change
|
||||
if (timeDifference <= 0) {
|
||||
@@ -332,31 +327,31 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
|
||||
*/
|
||||
double duration = timeDifference * UnitConversions.MS_TO_S;
|
||||
double speed = trackData.lastLocationInCurrentSegment.distanceTo(location) / duration;
|
||||
location.setSpeed((float) speed);
|
||||
double speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / duration;
|
||||
trackPoint.setSpeed((float) speed);
|
||||
}
|
||||
location.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(location));
|
||||
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
|
||||
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
|
||||
throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint));
|
||||
}
|
||||
|
||||
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
|
||||
// If not the first segment, add a resume separator before adding the first location.
|
||||
insertLocation(createLocation(TrackPointsColumns.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
|
||||
insertLocation(TrackPoint.createResumeWithTime(trackPoint.getTime()));
|
||||
}
|
||||
trackData.lastLocationInCurrentSegment = location;
|
||||
return location;
|
||||
trackData.lastLocationInCurrentSegment = trackPoint;
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a track point.
|
||||
*
|
||||
* @param location the location
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
protected void insertTrackPoint(Location location) {
|
||||
insertLocation(location);
|
||||
protected void insertTrackPoint(TrackPoint trackPoint) {
|
||||
insertLocation(trackPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,21 +366,42 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
/**
|
||||
* Gets the photo url for a file.
|
||||
*
|
||||
* @param fileName the file name
|
||||
* @param externalPhotoUrl the file name
|
||||
*/
|
||||
protected String getPhotoUrl(String fileName) {
|
||||
protected String getInternalPhotoUrl(String externalPhotoUrl) {
|
||||
if (importTrackId == -1L) {
|
||||
Log.e(TAG, "Track id is invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (externalPhotoUrl == null) {
|
||||
Log.i(TAG, "External photo url is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri externalPhotoUri = Uri.parse(externalPhotoUrl);
|
||||
if (externalPhotoUri == null) {
|
||||
Log.w(TAG, "Could not parse external photo url.");
|
||||
return null;
|
||||
}
|
||||
String filename = externalPhotoUri.getLastPathSegment();
|
||||
if (filename == null) {
|
||||
Log.w(TAG, "External photo contains no filename.");
|
||||
return null;
|
||||
}
|
||||
|
||||
File dir = FileUtils.getPhotoDir(context, importTrackId);
|
||||
File file = new File(dir, fileName);
|
||||
return Uri.fromFile(file).toString();
|
||||
File file = new File(dir, externalPhotoUrl);
|
||||
|
||||
Uri photoUri = FileUtils.getUriForFile(context, file);
|
||||
|
||||
return "" + photoUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location.
|
||||
*/
|
||||
private Location createLocation() throws SAXException {
|
||||
private TrackPoint createTrackPoint() throws SAXException {
|
||||
if (latitude == null || longitude == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -416,45 +432,21 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
throw new SAXException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
}
|
||||
}
|
||||
return createLocation(latitudeValue, longitudeValue, altitudeValue, timeValue);
|
||||
return new TrackPoint(latitudeValue, longitudeValue, altitudeValue, timeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a location.
|
||||
* Inserts a trackPoint.
|
||||
*
|
||||
* @param latitudeValue the latitude value
|
||||
* @param longitudeValue the longitude value
|
||||
* @param altitudeValue the altitude value
|
||||
* @param timeValue the time value
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
private Location createLocation(double latitudeValue, double longitudeValue, Double altitudeValue, long timeValue) {
|
||||
Location location = new Location(LocationManager.GPS_PROVIDER);
|
||||
location.setLatitude(latitudeValue);
|
||||
location.setLongitude(longitudeValue);
|
||||
if (altitudeValue != null) {
|
||||
location.setAltitude(altitudeValue);
|
||||
} else {
|
||||
location.removeAltitude();
|
||||
private void insertLocation(TrackPoint trackPoint) {
|
||||
if (trackData.trackStatisticsUpdater == null) {
|
||||
trackData.trackStatisticsUpdater = new TrackStatisticsUpdater(trackPoint.getTime() != -1L ? trackPoint.getTime() : trackData.importTime);
|
||||
}
|
||||
location.setTime(timeValue);
|
||||
location.removeAccuracy();
|
||||
location.removeBearing();
|
||||
location.removeSpeed();
|
||||
return location;
|
||||
}
|
||||
trackData.trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
|
||||
/**
|
||||
* Inserts a location.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
private void insertLocation(Location location) {
|
||||
if (trackData.tripStatisticsUpdater == null) {
|
||||
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(location.getTime() != -1L ? location.getTime() : trackData.importTime);
|
||||
}
|
||||
trackData.tripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
|
||||
trackData.bufferedLocations[trackData.numBufferedLocations] = location;
|
||||
trackData.bufferedTrackPoints[trackData.numBufferedLocations] = trackPoint;
|
||||
trackData.numBufferedLocations++;
|
||||
trackData.numberOfLocations++;
|
||||
|
||||
@@ -472,7 +464,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
if (data.numBufferedLocations <= 0) {
|
||||
return;
|
||||
}
|
||||
contentProviderUtils.bulkInsertTrackPoint(data.bufferedLocations, data.numBufferedLocations, data.track.getId());
|
||||
contentProviderUtils.bulkInsertTrackPoint(data.bufferedTrackPoints, data.numBufferedLocations, data.track.getId());
|
||||
data.numBufferedLocations = 0;
|
||||
}
|
||||
|
||||
@@ -491,7 +483,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
// TODO Why private inner class?
|
||||
private class TrackData {
|
||||
private static class TrackData {
|
||||
// The current track
|
||||
final Track track = new Track();
|
||||
|
||||
@@ -499,19 +491,19 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
int numberOfSegments = 0;
|
||||
|
||||
// The last location in the current segment; Null if the current segment doesn't have a last location
|
||||
Location lastLocationInCurrentSegment;
|
||||
TrackPoint lastLocationInCurrentSegment;
|
||||
|
||||
// The number of locations processed for the current track
|
||||
int numberOfLocations = 0;
|
||||
|
||||
// The trip statistics updater for the current track
|
||||
TripStatisticsUpdater tripStatisticsUpdater;
|
||||
// The TrackStatisticsUpdater for the current track
|
||||
TrackStatisticsUpdater trackStatisticsUpdater;
|
||||
|
||||
// The import time of the track.
|
||||
final long importTime = System.currentTimeMillis();
|
||||
|
||||
// The buffered locations
|
||||
final Location[] bufferedLocations = new Location[MAX_BUFFERED_LOCATIONS];
|
||||
final TrackPoint[] bufferedTrackPoints = new TrackPoint[MAX_BUFFERED_LOCATIONS];
|
||||
|
||||
// The number of buffered locations
|
||||
int numBufferedLocations = 0;
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
|
||||
/**
|
||||
@@ -155,11 +155,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
|
||||
* On track point end.
|
||||
*/
|
||||
private void onTrackPointEnd() throws SAXException {
|
||||
Location location = getTrackPoint();
|
||||
if (location == null) {
|
||||
TrackPoint trackPoint = getTrackPoint();
|
||||
if (trackPoint == null) {
|
||||
return;
|
||||
}
|
||||
insertTrackPoint(location);
|
||||
insertTrackPoint(trackPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,7 @@ import de.dennisguse.opentracks.util.SystemUtils;
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
|
||||
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
@@ -81,18 +81,6 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||
trackId = -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current {@link ImportActivity} associated with this AyncTask.
|
||||
*
|
||||
* @param importActivity the current {@link ImportActivity}, can be null
|
||||
*/
|
||||
public void setActivity(ImportActivity importActivity) {
|
||||
this.importActivity = importActivity;
|
||||
if (completed && importActivity != null) {
|
||||
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (importActivity != null) {
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
@@ -65,7 +63,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
|
||||
private boolean trackStarted = false;
|
||||
private String sensorType;
|
||||
private ArrayList<Location> locationList;
|
||||
private ArrayList<TrackPoint> trackPoints;
|
||||
private ArrayList<Float> cadenceList;
|
||||
private ArrayList<Float> heartRateList;
|
||||
private ArrayList<Float> powerList;
|
||||
@@ -187,10 +185,8 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
return;
|
||||
}
|
||||
|
||||
if (photoUrl != null) {
|
||||
Uri uri = Uri.parse(photoUrl);
|
||||
photoUrl = getPhotoUrl(uri.getLastPathSegment());
|
||||
}
|
||||
// If there is photoUrl it has to be changed because that url in kml file is a relative path to the internal kmz file.
|
||||
photoUrl = getInternalPhotoUrl(photoUrl);
|
||||
|
||||
addWaypoint();
|
||||
}
|
||||
@@ -213,7 +209,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
@Override
|
||||
protected void onTrackSegmentStart() {
|
||||
super.onTrackSegmentStart();
|
||||
locationList = new ArrayList<>();
|
||||
trackPoints = new ArrayList<>();
|
||||
heartRateList = new ArrayList<>();
|
||||
cadenceList = new ArrayList<>();
|
||||
powerList = new ArrayList<>();
|
||||
@@ -224,8 +220,8 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
*/
|
||||
private void onTrackSegmentEnd() {
|
||||
// Close a track segment by inserting the segment locations
|
||||
for (int i = 0; i < locationList.size(); i++) {
|
||||
Location location = locationList.get(i);
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
TrackPoint trackPoint = trackPoints.get(i);
|
||||
|
||||
boolean hasSensorData = false;
|
||||
float heartrate = SensorDataSet.DATA_UNAVAILABLE;
|
||||
@@ -246,9 +242,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
}
|
||||
|
||||
if (!hasSensorData) {
|
||||
insertTrackPoint(location);
|
||||
insertTrackPoint(trackPoint);
|
||||
} else {
|
||||
TrackPoint sensorDataSetLocation = new TrackPoint(location, new SensorDataSet(heartrate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime()));
|
||||
TrackPoint sensorDataSetLocation = new TrackPoint(trackPoint, new SensorDataSet(heartrate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, trackPoint.getTime()));
|
||||
insertTrackPoint(sensorDataSetLocation);
|
||||
}
|
||||
}
|
||||
@@ -270,11 +266,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
latitude = parts[1];
|
||||
altitude = parts.length == 3 ? parts[2] : null;
|
||||
|
||||
Location location = getTrackPoint();
|
||||
TrackPoint location = getTrackPoint();
|
||||
if (location == null) {
|
||||
return;
|
||||
}
|
||||
locationList.add(location);
|
||||
trackPoints.add(location);
|
||||
time = null;
|
||||
}
|
||||
|
||||
@@ -298,7 +294,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
}
|
||||
float value;
|
||||
try {
|
||||
value = Float.valueOf(content);
|
||||
value = Float.parseFloat(content);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.io.InputStream;
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public interface TrackImporter {
|
||||
interface TrackImporter {
|
||||
|
||||
/**
|
||||
* Import a file.
|
||||
|
||||
@@ -21,7 +21,7 @@ package de.dennisguse.opentracks.services;
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public interface LocationListenerPolicy {
|
||||
interface LocationListenerPolicy {
|
||||
|
||||
/**
|
||||
* Returns the polling interval this policy would like at this moment.
|
||||
|
||||
@@ -43,18 +43,16 @@ import de.dennisguse.opentracks.TrackDetailActivity;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.Waypoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointFactory;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TripStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
@@ -139,8 +137,8 @@ public class TrackRecordingService extends Service {
|
||||
private WakeLock wakeLock;
|
||||
private BluetoothRemoteSensorManager remoteSensorManager;
|
||||
|
||||
private TripStatisticsUpdater trackTripStatisticsUpdater;
|
||||
private Location lastLocation;
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||
private TrackPoint lastTrackPoint;
|
||||
private boolean currentSegmentHasLocation;
|
||||
private boolean isIdle;
|
||||
|
||||
@@ -261,21 +259,21 @@ public class TrackRecordingService extends Service {
|
||||
return recordingTrackId;
|
||||
}
|
||||
|
||||
public TripStatistics getTripStatistics() {
|
||||
if (trackTripStatisticsUpdater == null) {
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
if (trackStatisticsUpdater == null) {
|
||||
return null;
|
||||
}
|
||||
return trackTripStatisticsUpdater.getTripStatistics();
|
||||
return trackStatisticsUpdater.getTrackStatistics();
|
||||
}
|
||||
|
||||
public long getTotalTime() {
|
||||
if (trackTripStatisticsUpdater == null) {
|
||||
if (trackStatisticsUpdater == null) {
|
||||
return 0;
|
||||
}
|
||||
if (!isPaused()) {
|
||||
trackTripStatisticsUpdater.updateTime(System.currentTimeMillis());
|
||||
trackStatisticsUpdater.updateTime(System.currentTimeMillis());
|
||||
}
|
||||
return trackTripStatisticsUpdater.getTripStatistics().getTotalTime();
|
||||
return trackStatisticsUpdater.getTrackStatistics().getTotalTime();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,9 +294,9 @@ public class TrackRecordingService extends Service {
|
||||
name = getString(R.string.marker_name_format, nextWaypointNumber + 1);
|
||||
}
|
||||
|
||||
Location location = getLastValidTrackPointInCurrentSegment(recordingTrackId);
|
||||
if (location == null) {
|
||||
Log.i(TAG, "Could not create a waypoint as location is unknown.");
|
||||
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId);
|
||||
if (trackPoint == null) {
|
||||
Log.i(TAG, "Could not create a waypoint as trackPoint is unknown.");
|
||||
return -1L;
|
||||
}
|
||||
|
||||
@@ -307,12 +305,12 @@ public class TrackRecordingService extends Service {
|
||||
String icon = getString(R.string.marker_waypoint_icon_url);
|
||||
photoUrl = photoUrl != null ? photoUrl : "";
|
||||
|
||||
TripStatistics stats = trackTripStatisticsUpdater.getTripStatistics();
|
||||
TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics();
|
||||
double length = stats.getTotalDistance();
|
||||
long duration = stats.getTotalTime();
|
||||
|
||||
// Insert waypoint
|
||||
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, location, photoUrl);
|
||||
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, trackPoint.getLocation(), photoUrl);
|
||||
Uri uri = contentProviderUtils.insertWaypoint(waypoint);
|
||||
return ContentUris.parseId(uri);
|
||||
}
|
||||
@@ -328,7 +326,7 @@ public class TrackRecordingService extends Service {
|
||||
return -1L;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
trackTripStatisticsUpdater = new TripStatisticsUpdater(now);
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater(now);
|
||||
|
||||
// Insert a track
|
||||
Track track = new Track();
|
||||
@@ -345,21 +343,46 @@ public class TrackRecordingService extends Service {
|
||||
String category = PreferencesUtils.getDefaultActivity(this);
|
||||
track.setCategory(category);
|
||||
track.setIcon(TrackIconUtils.getIconValue(this, category));
|
||||
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
contentProviderUtils.updateTrack(track);
|
||||
|
||||
startRecording();
|
||||
return trackId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the track identified by trackId.
|
||||
* It results in a pause/continue.
|
||||
*
|
||||
* @param trackId the id of the track to be resumed.
|
||||
*/
|
||||
void resumeTrack(long trackId) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.e(TAG, "Ignore resumeTrack. Track " + trackId + " does not exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync the real time setting the stop time with current time.
|
||||
track.getTrackStatistics().setStopTime_ms(System.currentTimeMillis());
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
||||
|
||||
insertTrackPoint(track, TrackPoint.createPause(), null);
|
||||
insertTrackPoint(track, TrackPoint.createResume(), null);
|
||||
|
||||
// Update shared preferences.
|
||||
updateRecordingState(trackId, false);
|
||||
|
||||
startRecording();
|
||||
}
|
||||
|
||||
private void restartTrack(Track track) {
|
||||
Log.d(TAG, "Restarting track: " + track.getId());
|
||||
|
||||
TripStatistics tripStatistics = track.getTripStatistics();
|
||||
trackTripStatisticsUpdater = new TripStatisticsUpdater(tripStatistics.getStartTime());
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
|
||||
|
||||
try (TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, TrackPointFactory.DEFAULT_LOCATION_FACTORY)) {
|
||||
trackTripStatisticsUpdater.addLocation(locationIterator, recordingDistanceInterval);
|
||||
try (TrackPointIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
|
||||
trackStatisticsUpdater.addTrackPoint(locationIterator, recordingDistanceInterval);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "RuntimeException", e);
|
||||
}
|
||||
@@ -379,11 +402,7 @@ public class TrackRecordingService extends Service {
|
||||
// Update database
|
||||
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
||||
if (track != null) {
|
||||
Location resume = new Location(LocationManager.GPS_PROVIDER);
|
||||
resume.setLongitude(0);
|
||||
resume.setLatitude(TrackPointsColumns.RESUME_LATITUDE);
|
||||
resume.setTime(System.currentTimeMillis());
|
||||
insertLocation(track, resume, null);
|
||||
insertTrackPoint(track, TrackPoint.createResume(), null);
|
||||
}
|
||||
|
||||
startRecording();
|
||||
@@ -396,7 +415,7 @@ public class TrackRecordingService extends Service {
|
||||
// Update instance variables
|
||||
remoteSensorManager = new BluetoothRemoteSensorManager(this);
|
||||
remoteSensorManager.start();
|
||||
lastLocation = null;
|
||||
lastTrackPoint = null;
|
||||
currentSegmentHasLocation = false;
|
||||
isIdle = false;
|
||||
|
||||
@@ -436,13 +455,13 @@ public class TrackRecordingService extends Service {
|
||||
if (track != null) {
|
||||
// If not paused, add the last location
|
||||
if (!paused) {
|
||||
insertLocation(track, lastLocation, getLastValidTrackPointInCurrentSegment(trackId));
|
||||
insertTrackPoint(track, lastTrackPoint, getLastValidTrackPointInCurrentSegment(trackId));
|
||||
|
||||
// Update the recording track time
|
||||
updateRecordingTrack(track, false);
|
||||
updateRecordingTrack(track);
|
||||
}
|
||||
|
||||
String trackName = TrackNameUtils.getTrackName(this, trackId, track.getTripStatistics().getStartTime());
|
||||
String trackName = TrackNameUtils.getTrackName(this, trackId, track.getTrackStatistics().getStartTime_ms());
|
||||
if (trackName != null && !trackName.equals(track.getName())) {
|
||||
track.setName(trackName);
|
||||
contentProviderUtils.updateTrack(track);
|
||||
@@ -464,13 +483,9 @@ public class TrackRecordingService extends Service {
|
||||
// Update database
|
||||
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
||||
if (track != null) {
|
||||
insertLocation(track, lastLocation, getLastValidTrackPointInCurrentSegment(track.getId()));
|
||||
insertTrackPoint(track, lastTrackPoint, getLastValidTrackPointInCurrentSegment(track.getId()));
|
||||
|
||||
Location pause = new Location(LocationManager.GPS_PROVIDER);
|
||||
pause.setLongitude(0);
|
||||
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
|
||||
pause.setTime(System.currentTimeMillis());
|
||||
insertLocation(track, pause, null);
|
||||
insertTrackPoint(track, TrackPoint.createPause(), null);
|
||||
}
|
||||
|
||||
endRecording(false);
|
||||
@@ -492,7 +507,7 @@ public class TrackRecordingService extends Service {
|
||||
remoteSensorManager.stop();
|
||||
remoteSensorManager = null;
|
||||
}
|
||||
lastLocation = null;
|
||||
lastTrackPoint = null;
|
||||
|
||||
stopGps(trackStopped);
|
||||
}
|
||||
@@ -519,7 +534,7 @@ public class TrackRecordingService extends Service {
|
||||
* @param trackId the track id
|
||||
* @return the location or null
|
||||
*/
|
||||
private Location getLastValidTrackPointInCurrentSegment(long trackId) {
|
||||
private TrackPoint getLastValidTrackPointInCurrentSegment(long trackId) {
|
||||
if (!currentSegmentHasLocation) {
|
||||
return null;
|
||||
}
|
||||
@@ -556,7 +571,8 @@ public class TrackRecordingService extends Service {
|
||||
return;
|
||||
}
|
||||
|
||||
notificationManager.updateLocation(this, location, recordingGpsAccuracy);
|
||||
TrackPoint trackPoint = new TrackPoint(location, getSensorDataSet());
|
||||
notificationManager.updateTrackPoint(this, trackPoint, recordingGpsAccuracy);
|
||||
|
||||
if (!location.hasAccuracy() || location.getAccuracy() >= recordingGpsAccuracy) {
|
||||
Log.d(TAG, "Ignore onLocationChangedAsync. Poor accuracy.");
|
||||
@@ -569,88 +585,79 @@ public class TrackRecordingService extends Service {
|
||||
location.setTime(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
Location lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
long idleTime = 0L;
|
||||
if (lastValidTrackPoint != null && location.getTime() > lastValidTrackPoint.getTime()) {
|
||||
idleTime = location.getTime() - lastValidTrackPoint.getTime();
|
||||
if (lastValidTrackPoint != null && location.getTime() > lastValidTrackPoint.getLocation().getTime()) {
|
||||
idleTime = location.getTime() - lastValidTrackPoint.getLocation().getTime();
|
||||
}
|
||||
locationListenerPolicy.updateIdleTime(idleTime);
|
||||
if (currentRecordingInterval != locationListenerPolicy.getDesiredPollingInterval()) {
|
||||
registerLocationListener();
|
||||
}
|
||||
|
||||
SensorDataSet sensorDataSet = getSensorDataSet();
|
||||
if (sensorDataSet != null) {
|
||||
location = new TrackPoint(location, sensorDataSet);
|
||||
}
|
||||
|
||||
// Always insert the first segment location
|
||||
if (!currentSegmentHasLocation) {
|
||||
insertLocation(track, location, null);
|
||||
insertTrackPoint(track, trackPoint, null);
|
||||
currentSegmentHasLocation = true;
|
||||
lastLocation = location;
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(lastValidTrackPoint)) {
|
||||
if (!LocationUtils.isValidLocation(lastValidTrackPoint.getLocation())) {
|
||||
// Should not happen. The current segment should have a location. Just insert the current location.
|
||||
insertLocation(track, location, null);
|
||||
lastLocation = location;
|
||||
insertTrackPoint(track, trackPoint, null);
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
double distanceToLastTrackLocation = location.distanceTo(lastValidTrackPoint);
|
||||
double distanceToLastTrackLocation = location.distanceTo(lastValidTrackPoint.getLocation());
|
||||
if (distanceToLastTrackLocation > maxRecordingDistance) {
|
||||
insertLocation(track, lastLocation, lastValidTrackPoint);
|
||||
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
|
||||
insertTrackPoint(track, TrackPoint.createPause(), null);
|
||||
|
||||
Location pause = new Location(LocationManager.GPS_PROVIDER);
|
||||
pause.setLongitude(0);
|
||||
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
|
||||
pause.setTime(lastLocation.getTime());
|
||||
insertLocation(track, pause, null);
|
||||
|
||||
insertLocation(track, location, null);
|
||||
insertTrackPoint(track, trackPoint, null);
|
||||
isIdle = false;
|
||||
} else if (sensorDataSet != null || distanceToLastTrackLocation >= recordingDistanceInterval) {
|
||||
insertLocation(track, lastLocation, lastValidTrackPoint);
|
||||
insertLocation(track, location, null);
|
||||
} else if (trackPoint.getSensorDataSet() != null || distanceToLastTrackLocation >= recordingDistanceInterval) {
|
||||
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
|
||||
insertTrackPoint(track, trackPoint, null);
|
||||
isIdle = false;
|
||||
} else if (!isIdle && location.hasSpeed() && location.getSpeed() < MAX_NO_MOVEMENT_SPEED) {
|
||||
insertLocation(track, lastLocation, lastValidTrackPoint);
|
||||
insertLocation(track, location, null);
|
||||
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
|
||||
insertTrackPoint(track, trackPoint, null);
|
||||
isIdle = true;
|
||||
} else if (isIdle && location.hasSpeed() && location.getSpeed() >= MAX_NO_MOVEMENT_SPEED) {
|
||||
insertLocation(track, lastLocation, lastValidTrackPoint);
|
||||
insertLocation(track, location, null);
|
||||
insertTrackPoint(track, lastTrackPoint, lastValidTrackPoint);
|
||||
insertTrackPoint(track, trackPoint, null);
|
||||
isIdle = false;
|
||||
} else {
|
||||
Log.d(TAG, "Not recording location, idle");
|
||||
}
|
||||
lastLocation = location;
|
||||
lastTrackPoint = trackPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a location.
|
||||
* Inserts a trackPoint.
|
||||
*
|
||||
* @param track the track
|
||||
* @param location the location
|
||||
* @param trackPoint the trackPoint
|
||||
* @param lastValidTrackPoint the last valid track point, can be null
|
||||
*/
|
||||
private void insertLocation(Track track, Location location, Location lastValidTrackPoint) {
|
||||
if (location == null) {
|
||||
Log.w(TAG, "Ignore insertLocation. location is null.");
|
||||
private void insertTrackPoint(Track track, TrackPoint trackPoint, TrackPoint lastValidTrackPoint) {
|
||||
if (trackPoint == null) {
|
||||
Log.w(TAG, "Ignore insertLocation. trackPoint is null.");
|
||||
return;
|
||||
}
|
||||
// Do not insert if inserted already
|
||||
if (lastValidTrackPoint != null && lastValidTrackPoint.getTime() == location.getTime()) {
|
||||
Log.w(TAG, "Ignore insertLocation. location time same as last valid track point time.");
|
||||
if (lastValidTrackPoint != null && lastValidTrackPoint.getTime() == trackPoint.getTime()) {
|
||||
Log.w(TAG, "Ignore insertLocation. trackPoint time same as last valid track point time.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
contentProviderUtils.insertTrackPoint(location, track.getId());
|
||||
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval);
|
||||
updateRecordingTrack(track, LocationUtils.isValidLocation(location));
|
||||
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
updateRecordingTrack(track);
|
||||
} catch (SQLiteException e) {
|
||||
/*
|
||||
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
|
||||
@@ -663,18 +670,12 @@ public class TrackRecordingService extends Service {
|
||||
|
||||
/**
|
||||
* Updates the recording track time.
|
||||
* Increase the number of points if it is a new and valid track point.
|
||||
*
|
||||
* @param track the track
|
||||
* @param increaseNumberOfPoints true to increase the number of points
|
||||
* @param track the track
|
||||
*/
|
||||
private void updateRecordingTrack(Track track, boolean increaseNumberOfPoints) {
|
||||
if (increaseNumberOfPoints) {
|
||||
track.setNumberOfPoints(track.getNumberOfPoints() + 1);
|
||||
}
|
||||
|
||||
trackTripStatisticsUpdater.updateTime(System.currentTimeMillis());
|
||||
track.setTripStatistics(trackTripStatisticsUpdater.getTripStatistics());
|
||||
private void updateRecordingTrack(Track track) {
|
||||
trackStatisticsUpdater.updateTime(System.currentTimeMillis());
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
contentProviderUtils.updateTrack(track);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
|
||||
return trackRecordingService.startNewTrack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeTrack(long trackId) {
|
||||
trackRecordingService.resumeTrack(trackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseCurrentTrack() {
|
||||
trackRecordingService.pauseCurrentTrack();
|
||||
@@ -78,7 +83,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void insertTrackPoint(Location location) {
|
||||
public void insertLocation(Location location) {
|
||||
trackRecordingService.onLocationChangedAsync(location);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,10 +54,9 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param context the context
|
||||
* @param callback the callback to invoke when the service binding changes
|
||||
*/
|
||||
public TrackRecordingServiceConnection(Context context, Runnable callback) {
|
||||
public TrackRecordingServiceConnection(Runnable callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@@ -71,7 +70,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
|
||||
/**
|
||||
* Binds the service if it is started.
|
||||
*/
|
||||
public void bindIfStarted(Context context) {
|
||||
private void bindIfStarted(Context context) {
|
||||
bindService(context, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,13 @@ public interface TrackRecordingServiceInterface {
|
||||
*/
|
||||
long startNewTrack();
|
||||
|
||||
/**
|
||||
* Resumes the track identified by trackId.
|
||||
*
|
||||
* @param trackId
|
||||
*/
|
||||
void resumeTrack(long trackId);
|
||||
|
||||
/**
|
||||
* Pauses the current recording track.
|
||||
*/
|
||||
@@ -89,15 +96,15 @@ public interface TrackRecordingServiceInterface {
|
||||
/**
|
||||
* ONLY FOR TESTING
|
||||
*
|
||||
* Inserts a track point in the current recording track.
|
||||
* Inserts a location in the current recording track.
|
||||
* <p>
|
||||
* When recording a track, GPS locations are automatically inserted. This is used for
|
||||
* inserting special track points or for testing.
|
||||
* When recording a track, GPS locations are automatically inserted.
|
||||
* This is used for inserting special track points or for testing.
|
||||
*
|
||||
* @param location the track point to be inserted
|
||||
* @param location the location to be inserted
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void insertTrackPoint(Location location);
|
||||
void insertLocation(Location location);
|
||||
|
||||
/**
|
||||
* Gets the current sensor data. Returns null if there is no data.
|
||||
|
||||
@@ -5,13 +5,13 @@ import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
@@ -63,12 +63,12 @@ class TrackRecordingServiceNotificationManager {
|
||||
updateNotification();
|
||||
}
|
||||
|
||||
void updateLocation(Context context, Location location, int recordingGpsAccuracy) {
|
||||
void updateTrackPoint(Context context, TrackPoint trackPoint, int recordingGpsAccuracy) {
|
||||
String formattedAccuracy = context.getString(R.string.value_none);
|
||||
if (location.hasAccuracy()) {
|
||||
formattedAccuracy = StringUtils.formatDistance(context, location.getAccuracy(), PreferencesUtils.isMetricUnits(context));
|
||||
if (trackPoint.hasAccuracy()) {
|
||||
formattedAccuracy = StringUtils.formatDistance(context, trackPoint.getAccuracy(), PreferencesUtils.isMetricUnits(context));
|
||||
|
||||
boolean currentLocationWasAccurate = location.getAccuracy() < recordingGpsAccuracy;
|
||||
boolean currentLocationWasAccurate = trackPoint.getAccuracy() < recordingGpsAccuracy;
|
||||
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
|
||||
notificationBuilder.setOnlyAlertOnce(!shouldAlert);
|
||||
previousLocationWasAccurate = currentLocationWasAccurate;
|
||||
|
||||
@@ -25,15 +25,12 @@ import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.AnnouncementUtils;
|
||||
|
||||
/**
|
||||
* This class will periodically announce the user's trip statistics.
|
||||
* This class will periodically announce the user's {@link TrackStatistics}.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
@@ -129,17 +126,17 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
Log.e(TAG, "TrackRecordingService is null.");
|
||||
return;
|
||||
}
|
||||
announce(trackRecordingService.getTripStatistics());
|
||||
announce(trackRecordingService.getTrackStatistics());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs this task.
|
||||
*
|
||||
* @param tripStatistics the trip statistics
|
||||
* @param trackStatistics the track statistics
|
||||
*/
|
||||
private void announce(TripStatistics tripStatistics) {
|
||||
if (tripStatistics == null) {
|
||||
Log.e(TAG, "TripStatistics is null.");
|
||||
private void announce(TrackStatistics trackStatistics) {
|
||||
if (trackStatistics == null) {
|
||||
Log.e(TAG, "TrackStatistics is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,7 +157,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
Log.i(TAG, "Speech is not allowed at this time.");
|
||||
return;
|
||||
}
|
||||
speakAnnouncement(getAnnouncement(tripStatistics));
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics);
|
||||
speakAnnouncement(announcement);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -169,8 +167,6 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
tts.shutdown();
|
||||
tts = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void onTtsReady() {
|
||||
@@ -196,81 +192,4 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
// We don't care about the utterance id. It is supplied here to force onUtteranceCompleted to be called.
|
||||
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null, "not used");
|
||||
}
|
||||
|
||||
private String getAnnouncement(TripStatistics tripStatistics) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
double distancePerTime = tripStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
|
||||
|
||||
if (distance == 0) {
|
||||
return context.getString(R.string.voice_total_distance_zero);
|
||||
}
|
||||
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
distancePerTime *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
String rate;
|
||||
if (reportSpeed) {
|
||||
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
|
||||
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
|
||||
} else {
|
||||
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
|
||||
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
|
||||
long time = Math.round(timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
||||
rate = context.getString(paceId, getAnnounceTime(time));
|
||||
}
|
||||
|
||||
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
||||
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
|
||||
|
||||
return context.getString(R.string.voice_template, totalDistance,
|
||||
getAnnounceTime(tripStatistics.getMovingTime()), rate);
|
||||
}
|
||||
|
||||
private String getAnnounceTime(long time) {
|
||||
int[] parts = StringUtils.getTimeParts(time);
|
||||
String seconds = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
|
||||
String minutes = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceMinutes, parts[1], parts[1]);
|
||||
String hours = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceHours, parts[2], parts[2]);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (parts[2] != 0) {
|
||||
sb.append(hours);
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(minutes);
|
||||
sb.append(" ");
|
||||
sb.append(seconds);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plural count to be used by getQuantityString.
|
||||
* getQuantityString only supports integer quantities, not a double quantity like "2.2".
|
||||
* <p>
|
||||
* As a temporary workaround, we convert a double quantity to an integer quantity.
|
||||
* If the double quantity is exactly 0, 1, or 2, then we can return these integer quantities.
|
||||
* Otherwise, we cast the double quantity to an integer quantity.
|
||||
* However, we need to make sure that if the casted value is 0, 1, or 2, we don't return those, instead, return the next biggest integer 3.
|
||||
*
|
||||
* @param d the double value
|
||||
*/
|
||||
private int getQuantityCount(double d) {
|
||||
if (d == 0) {
|
||||
return 0;
|
||||
} else if (d == 1) {
|
||||
return 1;
|
||||
} else if (d == 2) {
|
||||
return 2;
|
||||
} else {
|
||||
//TODO This seems weird; why not use Math.round(d) or Math.ceil()?
|
||||
int count = (int) d;
|
||||
return count < 3 ? 3 : count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import android.util.Log;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,6 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
public class PeriodicTaskExecutor {
|
||||
|
||||
private static final String TAG = PeriodicTaskExecutor.class.getSimpleName();
|
||||
private static final long MINUTE_TO_MILLISECONDS = 60000L;
|
||||
|
||||
private final TrackRecordingService trackRecordingService;
|
||||
private final PeriodicTaskFactory periodicTaskFactory;
|
||||
@@ -93,7 +92,7 @@ public class PeriodicTaskExecutor {
|
||||
if (timerTaskExecutor == null) {
|
||||
timerTaskExecutor = new TimerTaskExecutor(periodicTask, trackRecordingService);
|
||||
}
|
||||
timerTaskExecutor.scheduleTask(taskFrequency * MINUTE_TO_MILLISECONDS);
|
||||
timerTaskExecutor.scheduleTask(taskFrequency * UnitConversions.ONE_MINUTE_MS);
|
||||
} else {
|
||||
// For distance periodic task
|
||||
calculateNextTaskDistance();
|
||||
@@ -122,12 +121,12 @@ public class PeriodicTaskExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
TripStatistics tripStatistics = trackRecordingService.getTripStatistics();
|
||||
if (tripStatistics == null) {
|
||||
TrackStatistics trackStatistics = trackRecordingService.getTrackStatistics();
|
||||
if (trackStatistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
@@ -166,8 +165,8 @@ public class PeriodicTaskExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
TripStatistics tripStatistics = trackRecordingService.getTripStatistics();
|
||||
if (tripStatistics == null) {
|
||||
TrackStatistics trackStatistics = trackRecordingService.getTrackStatistics();
|
||||
if (trackStatistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,7 +176,7 @@ public class PeriodicTaskExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import android.content.Context;
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public interface PeriodicTaskFactory {
|
||||
interface PeriodicTaskFactory {
|
||||
|
||||
/**
|
||||
* Creates a {@link PeriodicTask}.
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TripStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* This class will periodically perform a task.
|
||||
@@ -55,8 +55,8 @@ class TimerTaskExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
TripStatistics tripStatistics = trackRecordingService.getTripStatistics();
|
||||
if (tripStatistics == null) {
|
||||
TrackStatistics trackStatistics = trackRecordingService.getTrackStatistics();
|
||||
if (trackStatistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class TimerTaskExecutor {
|
||||
}
|
||||
};
|
||||
timer = new Timer(TimerTaskExecutor.class.getSimpleName());
|
||||
long next = System.currentTimeMillis() + interval_ms - (tripStatistics.getTotalTime() % interval_ms);
|
||||
long next = System.currentTimeMillis() + interval_ms - (trackStatistics.getTotalTime() % interval_ms);
|
||||
timer.scheduleAtFixedRate(timerTask, new Date(next), interval_ms);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
||||
return devices.get(index);
|
||||
}
|
||||
|
||||
public class Device {
|
||||
public static class Device {
|
||||
private String name;
|
||||
private final String address;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class DoubleBuffer {
|
||||
class DoubleBuffer {
|
||||
|
||||
// The sliding buffer of doubles.
|
||||
private final double[] buffer;
|
||||
|
||||
@@ -19,22 +19,20 @@ package de.dennisguse.opentracks.stats;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Statistical data about a trip.
|
||||
* The data in this class should be filled out by TripStatisticsBuilder.
|
||||
* Statistical data about a {@link de.dennisguse.opentracks.content.data.Track}.
|
||||
* The data in this class should be filled out by {@link TrackStatisticsUpdater}.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TripStatistics {
|
||||
public class TrackStatistics {
|
||||
|
||||
// The min and max elevation (meters) seen on this trip.
|
||||
// The min and max elevation (meters) seen on this track.
|
||||
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
|
||||
// The min and max grade seen on this trip.
|
||||
private final ExtremityMonitor gradeExtremities = new ExtremityMonitor();
|
||||
|
||||
// The trip start time. This is the system time, might not match the GPs time.
|
||||
private long startTime = -1L;
|
||||
// The trip stop time. This is the system time, might not match the GPS time.
|
||||
private long stopTime = -1L;
|
||||
// The track start time. This is the system time, might not match the GPs time.
|
||||
private long startTime_ms = -1L;
|
||||
// The track stop time. This is the system time, might not match the GPS time.
|
||||
private long stopTime_ms = -1L;
|
||||
|
||||
private double totalDistance_m;
|
||||
// Updated when new points are received, may be stale.
|
||||
@@ -46,7 +44,7 @@ public class TripStatistics {
|
||||
// The total elevation gained (meters).
|
||||
private double totalElevationGain_m;
|
||||
|
||||
public TripStatistics() {
|
||||
public TrackStatistics() {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,16 +52,15 @@ public class TripStatistics {
|
||||
*
|
||||
* @param other another statistics data object to copy from
|
||||
*/
|
||||
public TripStatistics(TripStatistics other) {
|
||||
startTime = other.startTime;
|
||||
stopTime = other.stopTime;
|
||||
public TrackStatistics(TrackStatistics other) {
|
||||
startTime_ms = other.startTime_ms;
|
||||
stopTime_ms = other.stopTime_ms;
|
||||
totalDistance_m = other.totalDistance_m;
|
||||
totalTime_ms = other.totalTime_ms;
|
||||
movingTime_ms = other.movingTime_ms;
|
||||
maxSpeed_mps = other.maxSpeed_mps;
|
||||
elevationExtremities.set(other.elevationExtremities.getMin(), other.elevationExtremities.getMax());
|
||||
totalElevationGain_m = other.totalElevationGain_m;
|
||||
gradeExtremities.set(other.gradeExtremities.getMin(), other.gradeExtremities.getMax());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,9 +69,9 @@ public class TripStatistics {
|
||||
*
|
||||
* @param other another statistics data object
|
||||
*/
|
||||
public void merge(TripStatistics other) {
|
||||
startTime = Math.min(startTime, other.startTime);
|
||||
stopTime = Math.max(stopTime, other.stopTime);
|
||||
public void merge(TrackStatistics other) {
|
||||
startTime_ms = Math.min(startTime_ms, other.startTime_ms);
|
||||
stopTime_ms = Math.max(stopTime_ms, other.stopTime_ms);
|
||||
totalDistance_m += other.totalDistance_m;
|
||||
totalTime_ms += other.totalTime_ms;
|
||||
movingTime_ms += other.movingTime_ms;
|
||||
@@ -84,29 +81,25 @@ public class TripStatistics {
|
||||
elevationExtremities.update(other.elevationExtremities.getMax());
|
||||
}
|
||||
totalElevationGain_m += other.totalElevationGain_m;
|
||||
if (other.gradeExtremities.hasData()) {
|
||||
gradeExtremities.update(other.gradeExtremities.getMin());
|
||||
gradeExtremities.update(other.gradeExtremities.getMax());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the trip start time. The number of milliseconds since epoch.
|
||||
* Gets the track start time. The number of milliseconds since epoch.
|
||||
*/
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
public long getStartTime_ms() {
|
||||
return startTime_ms;
|
||||
}
|
||||
|
||||
public void setStartTime(long startTime) {
|
||||
this.startTime = startTime;
|
||||
public void setStartTime_ms(long startTime_ms) {
|
||||
this.startTime_ms = startTime_ms;
|
||||
}
|
||||
|
||||
public long getStopTime() {
|
||||
return stopTime;
|
||||
public long getStopTime_ms() {
|
||||
return stopTime_ms;
|
||||
}
|
||||
|
||||
public void setStopTime(long stopTime) {
|
||||
this.stopTime = stopTime;
|
||||
public void setStopTime_ms(long stopTime_ms) {
|
||||
this.stopTime_ms = stopTime_ms;
|
||||
}
|
||||
|
||||
public double getTotalDistance() {
|
||||
@@ -124,7 +117,7 @@ public class TripStatistics {
|
||||
/**
|
||||
* Gets the total time in milliseconds that this track has been active.
|
||||
* This statistic is only updated when a new point is added to the statistics, so it may be off.
|
||||
* If you need to calculate the proper total time, use {@link #getStartTime} with the current time.
|
||||
* If you need to calculate the proper total time, use {@link #getStartTime_ms} with the current time.
|
||||
*/
|
||||
public long getTotalTime() {
|
||||
return totalTime_ms;
|
||||
@@ -241,39 +234,13 @@ public class TripStatistics {
|
||||
totalElevationGain_m += gain_m;
|
||||
}
|
||||
|
||||
public double getMinGrade() {
|
||||
return gradeExtremities.getMin();
|
||||
}
|
||||
|
||||
public void setMinGrade(double grade) {
|
||||
gradeExtremities.setMin(grade);
|
||||
}
|
||||
|
||||
public double getMaxGrade() {
|
||||
return gradeExtremities.getMax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum grade.
|
||||
*
|
||||
* @param grade the grade as a fraction (1.0 would mean vertical upwards)
|
||||
*/
|
||||
public void setMaxGrade(double grade) {
|
||||
gradeExtremities.setMax(grade);
|
||||
}
|
||||
|
||||
public void updateGradeExtremities(double grade) {
|
||||
gradeExtremities.update(grade);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TripStatistics { Start Time: " + getStartTime() + "; Stop Time: " + getStopTime()
|
||||
return "TrackStatistics { Start Time: " + getStartTime_ms() + "; Stop Time: " + getStopTime_ms()
|
||||
+ "; Total Distance: " + getTotalDistance() + "; Total Time: " + getTotalTime()
|
||||
+ "; Moving Time: " + getMovingTime() + "; Max Speed: " + getMaxSpeed()
|
||||
+ "; Min Elevation: " + getMinElevation() + "; Max Elevation: " + getMaxElevation()
|
||||
+ "; Elevation Gain: " + getTotalElevationGain() + "; Min Grade: " + getMinGrade()
|
||||
+ "; Max Grade: " + getMaxGrade() + "}";
|
||||
+ "; Elevation Gain: " + getTotalElevationGain() + "}";
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
@@ -28,38 +28,28 @@ import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import static de.dennisguse.opentracks.services.TrackRecordingService.MAX_NO_MOVEMENT_SPEED;
|
||||
|
||||
/**
|
||||
* Updater for {@link TripStatistics}.
|
||||
* For updating track trip statistics as new locations are added.
|
||||
* NOTE:Some of the locations represent pause/resume separator.
|
||||
* Updater for {@link TrackStatistics}.
|
||||
* For updating track {@link TrackStatistics} as new {@link TrackPoint}s are added.
|
||||
* NOTE: Some of the locations represent pause/resume separator.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TripStatisticsUpdater {
|
||||
public class TrackStatisticsUpdater {
|
||||
|
||||
/**
|
||||
* The number of grade readings to smooth to get a somewhat accurate signal.
|
||||
*/
|
||||
public static final int GRADE_SMOOTHING_FACTOR = 5;
|
||||
/**
|
||||
* The number of elevation readings to smooth to get a somewhat accurate
|
||||
* signal.
|
||||
* The number of elevation readings to smooth to get a somewhat accurate signal.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private static final int ELEVATION_SMOOTHING_FACTOR = 25;
|
||||
|
||||
/**
|
||||
* The number of run readings to smooth for calculating grade.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private static final int RUN_SMOOTHING_FACTOR = 25;
|
||||
/**
|
||||
* The number of speed reading to smooth to get a somewhat accurate signal.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private static final int SPEED_SMOOTHING_FACTOR = 25;
|
||||
|
||||
private static final String TAG = TripStatisticsUpdater.class.getSimpleName();
|
||||
private static final String TAG = TrackStatisticsUpdater.class.getSimpleName();
|
||||
/**
|
||||
* Ignore any acceleration faster than this.
|
||||
* Will ignore any speeds that imply acceleration greater than 2g's
|
||||
@@ -67,96 +57,100 @@ public class TripStatisticsUpdater {
|
||||
*/
|
||||
private static final double MAX_ACCELERATION = 0.02;
|
||||
|
||||
// The track's trip statistics
|
||||
private final TripStatistics tripStatistics;
|
||||
// The track's statistics
|
||||
private final TrackStatistics trackStatistics;
|
||||
|
||||
// A buffer of the recent elevation readings (m)
|
||||
private final DoubleBuffer elevationBuffer = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR);
|
||||
// A buffer of the recent run readings (m) for calculating grade
|
||||
private final DoubleBuffer runBuffer = new DoubleBuffer(RUN_SMOOTHING_FACTOR);
|
||||
// A buffer of the recent grade calculations (%)
|
||||
private final DoubleBuffer gradeBuffer = new DoubleBuffer(GRADE_SMOOTHING_FACTOR);
|
||||
private final DoubleBuffer elevationBuffer_m = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR);
|
||||
// A buffer of the recent speed readings (m/s) for calculating max speed
|
||||
private final DoubleBuffer speedBuffer = new DoubleBuffer(SPEED_SMOOTHING_FACTOR);
|
||||
private final DoubleBuffer speedBuffer_ms = new DoubleBuffer(SPEED_SMOOTHING_FACTOR);
|
||||
|
||||
// The current segment's trip statistics
|
||||
private TripStatistics currentSegment;
|
||||
// Current segment's last location.
|
||||
private Location lastLocation;
|
||||
// Current segment's last moving location
|
||||
private Location lastMovingLocation;
|
||||
// The current segment's statistics
|
||||
private TrackStatistics currentSegment;
|
||||
// Current segment's last trackPoint
|
||||
private TrackPoint lastTrackPoint;
|
||||
// Current segment's last moving trackPoint
|
||||
private TrackPoint lastMovingTrackPoint;
|
||||
|
||||
/**
|
||||
* Creates a new trip statistics updater.
|
||||
* Creates a new {@link TrackStatisticsUpdater}.
|
||||
*
|
||||
* @param startTime the start time
|
||||
* @param startTime_ms the start time in milliseconds
|
||||
*/
|
||||
public TripStatisticsUpdater(long startTime) {
|
||||
tripStatistics = init(startTime);
|
||||
currentSegment = init(startTime);
|
||||
public TrackStatisticsUpdater(long startTime_ms) {
|
||||
trackStatistics = init(startTime_ms);
|
||||
currentSegment = init(startTime_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new{@link TrackStatisticsUpdater} with a {@link TrackStatisticsUpdater} already existed.
|
||||
*
|
||||
* @param trackStatistics a {@link TrackStatisticsUpdater}
|
||||
*/
|
||||
public TrackStatisticsUpdater(TrackStatistics trackStatistics) {
|
||||
this.trackStatistics = trackStatistics;
|
||||
currentSegment = init(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void updateTime(long time) {
|
||||
currentSegment.setStopTime(time);
|
||||
currentSegment.setTotalTime(time - currentSegment.getStartTime());
|
||||
currentSegment.setStopTime_ms(time);
|
||||
currentSegment.setTotalTime(time - currentSegment.getStartTime_ms());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the track's trip statistics.
|
||||
* Gets the track's statistics.
|
||||
*/
|
||||
public TripStatistics getTripStatistics() {
|
||||
// Take a snapshot - we don't want anyone messing with our tripStatistics
|
||||
TripStatistics stats = new TripStatistics(tripStatistics);
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
// Take a snapshot - we don't want anyone messing with our trackStatistics
|
||||
TrackStatistics stats = new TrackStatistics(trackStatistics);
|
||||
stats.merge(currentSegment);
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a location.
|
||||
* TODO: This assume location has a valid time.
|
||||
* Adds a trackPoint.
|
||||
* TODO: This assume trackPoint has a valid time.
|
||||
*
|
||||
* @param location the location
|
||||
* @param trackPoint the trackPoint
|
||||
* @param minRecordingDistance the min recording distance
|
||||
*/
|
||||
public void addLocation(Location location, int minRecordingDistance) {
|
||||
public void addTrackPoint(TrackPoint trackPoint, int minRecordingDistance) {
|
||||
// Always update time
|
||||
updateTime(location.getTime());
|
||||
if (!LocationUtils.isValidLocation(location)) {
|
||||
updateTime(trackPoint.getLocation().getTime());
|
||||
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
|
||||
// Either pause or resume marker
|
||||
if (location.getLatitude() == TrackPointsColumns.PAUSE_LATITUDE) {
|
||||
if (lastLocation != null && lastMovingLocation != null && lastLocation != lastMovingLocation) {
|
||||
currentSegment.addTotalDistance(lastMovingLocation.distanceTo(lastLocation));
|
||||
if (trackPoint.getLocation().getLatitude() == TrackPointsColumns.PAUSE_LATITUDE) {
|
||||
if (lastTrackPoint != null && lastMovingTrackPoint != null && lastTrackPoint != lastMovingTrackPoint) {
|
||||
currentSegment.addTotalDistance(lastMovingTrackPoint.distanceTo(lastTrackPoint));
|
||||
}
|
||||
tripStatistics.merge(currentSegment);
|
||||
trackStatistics.merge(currentSegment);
|
||||
}
|
||||
currentSegment = init(location.getTime());
|
||||
lastLocation = null;
|
||||
lastMovingLocation = null;
|
||||
elevationBuffer.reset();
|
||||
runBuffer.reset();
|
||||
gradeBuffer.reset();
|
||||
speedBuffer.reset();
|
||||
currentSegment = init(trackPoint.getLocation().getTime());
|
||||
lastTrackPoint = null;
|
||||
lastMovingTrackPoint = null;
|
||||
elevationBuffer_m.reset();
|
||||
speedBuffer_ms.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO Use Barometer to compute elevation gain.
|
||||
double elevationDifference = location.hasAltitude() ? updateElevation(location.getAltitude()) : 0.0;
|
||||
double elevationDifference = trackPoint.hasAltitude() ? updateElevation(trackPoint.getAltitude()) : 0.0;
|
||||
|
||||
if (lastLocation == null || lastMovingLocation == null) {
|
||||
lastLocation = location;
|
||||
lastMovingLocation = location;
|
||||
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
||||
lastTrackPoint = trackPoint;
|
||||
lastMovingTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
double movingDistance = lastMovingLocation.distanceTo(location);
|
||||
if (movingDistance < minRecordingDistance && (!location.hasSpeed() || location.getSpeed() < MAX_NO_MOVEMENT_SPEED)) {
|
||||
speedBuffer.reset();
|
||||
lastLocation = location;
|
||||
double movingDistance = lastMovingTrackPoint.distanceTo(trackPoint);
|
||||
if (movingDistance < minRecordingDistance && (!trackPoint.hasSpeed() || trackPoint.getSpeed() < MAX_NO_MOVEMENT_SPEED)) {
|
||||
speedBuffer_ms.reset();
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
long movingTime = location.getTime() - lastLocation.getTime();
|
||||
long movingTime = trackPoint.getTime() - lastTrackPoint.getTime();
|
||||
if (movingTime < 0) {
|
||||
lastLocation = location;
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,23 +160,19 @@ public class TripStatisticsUpdater {
|
||||
// Update moving time
|
||||
currentSegment.addMovingTime(movingTime);
|
||||
|
||||
// Update grade
|
||||
double run = lastLocation.distanceTo(location);
|
||||
updateGrade(run, elevationDifference);
|
||||
|
||||
// Update max speed
|
||||
if (location.hasSpeed() && lastLocation.hasSpeed()) {
|
||||
updateSpeed(location.getTime(), location.getSpeed(), lastLocation.getTime(), lastLocation.getSpeed());
|
||||
if (trackPoint.hasSpeed() && lastTrackPoint.hasSpeed()) {
|
||||
updateSpeed(trackPoint.getTime(), trackPoint.getSpeed(), lastTrackPoint.getTime(), lastTrackPoint.getSpeed());
|
||||
}
|
||||
|
||||
lastLocation = location;
|
||||
lastMovingLocation = location;
|
||||
lastTrackPoint = trackPoint;
|
||||
lastMovingTrackPoint = trackPoint;
|
||||
}
|
||||
|
||||
public void addLocation(TrackPointIterator iterator, int minRecordingDistance) {
|
||||
public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) {
|
||||
while (iterator.hasNext()) {
|
||||
Location location = iterator.next();
|
||||
addLocation(location, minRecordingDistance);
|
||||
TrackPoint location = iterator.next();
|
||||
addTrackPoint(location, minRecordingDistance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,11 +181,11 @@ public class TripStatisticsUpdater {
|
||||
* The elevation readings is noisy so the smoothed elevation is better than the raw elevation for many tasks.
|
||||
*/
|
||||
public double getSmoothedElevation() {
|
||||
return elevationBuffer.getAverage();
|
||||
return elevationBuffer_m.getAverage();
|
||||
}
|
||||
|
||||
public double getSmoothedSpeed() {
|
||||
return speedBuffer.getAverage();
|
||||
return speedBuffer_ms.getAverage();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,11 +199,11 @@ public class TripStatisticsUpdater {
|
||||
@VisibleForTesting
|
||||
private void updateSpeed(long time, double speed, long lastLocationTime, double lastLocationSpeed) {
|
||||
if (speed < MAX_NO_MOVEMENT_SPEED) {
|
||||
speedBuffer.reset();
|
||||
speedBuffer_ms.reset();
|
||||
} else if (isValidSpeed(time, speed, lastLocationTime, lastLocationSpeed)) {
|
||||
speedBuffer.setNext(speed);
|
||||
if (speedBuffer.getAverage() > currentSegment.getMaxSpeed()) {
|
||||
currentSegment.setMaxSpeed(speedBuffer.getAverage());
|
||||
speedBuffer_ms.setNext(speed);
|
||||
if (speedBuffer_ms.getAverage() > currentSegment.getMaxSpeed()) {
|
||||
currentSegment.setMaxSpeed(speedBuffer_ms.getAverage());
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Invalid speed. speed: " + speed + " lastLocationSpeed: " + lastLocationSpeed);
|
||||
@@ -228,9 +218,9 @@ public class TripStatisticsUpdater {
|
||||
@VisibleForTesting
|
||||
private double updateElevation(double elevation) {
|
||||
// Update elevation using the smoothed average
|
||||
double oldAverage = elevationBuffer.getAverage();
|
||||
elevationBuffer.setNext(elevation);
|
||||
double newAverage = elevationBuffer.getAverage();
|
||||
double oldAverage = elevationBuffer_m.getAverage();
|
||||
elevationBuffer_m.setNext(elevation);
|
||||
double newAverage = elevationBuffer_m.getAverage();
|
||||
|
||||
currentSegment.updateElevationExtremities(newAverage);
|
||||
double difference = newAverage - oldAverage;
|
||||
@@ -240,30 +230,10 @@ public class TripStatisticsUpdater {
|
||||
return difference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a grade reading.
|
||||
*
|
||||
* @param run the run
|
||||
* @param rise the rise
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private void updateGrade(double run, double rise) {
|
||||
runBuffer.setNext(run);
|
||||
|
||||
double smoothedRun = runBuffer.getAverage();
|
||||
|
||||
// With the error in the altitude measurement, it is dangerous to divide by * anything less than 5.
|
||||
if (smoothedRun < 5.0) {
|
||||
return;
|
||||
}
|
||||
gradeBuffer.setNext(rise / smoothedRun);
|
||||
currentSegment.updateGradeExtremities(gradeBuffer.getAverage());
|
||||
}
|
||||
|
||||
private TripStatistics init(long time) {
|
||||
TripStatistics stats = new TripStatistics();
|
||||
stats.setStartTime(time);
|
||||
stats.setStopTime(time);
|
||||
private TrackStatistics init(long time) {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setStartTime_ms(time);
|
||||
stats.setStopTime_ms(time);
|
||||
return stats;
|
||||
}
|
||||
|
||||
@@ -294,8 +264,8 @@ public class TripStatisticsUpdater {
|
||||
}
|
||||
|
||||
// Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration.
|
||||
if (speedBuffer.isFull()) {
|
||||
double average = speedBuffer.getAverage();
|
||||
if (speedBuffer_ms.isFull()) {
|
||||
double average = speedBuffer_ms.getAverage();
|
||||
double diff = Math.abs(average - speed);
|
||||
return (speed < average * 10) && (diff < MAX_ACCELERATION * timeDifference);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
public class AnnouncementUtils {
|
||||
|
||||
private AnnouncementUtils() {}
|
||||
|
||||
public static String getAnnouncement(Context context, TrackStatistics trackStatistics) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
double distancePerTime = trackStatistics.getAverageMovingSpeed() * UnitConversions.MS_TO_KMH;
|
||||
|
||||
if (distance == 0) {
|
||||
return context.getString(R.string.voice_total_distance_zero);
|
||||
}
|
||||
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
distancePerTime *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
String rate;
|
||||
if (reportSpeed) {
|
||||
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
|
||||
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
|
||||
} else {
|
||||
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
|
||||
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
|
||||
long time = Math.round(timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
||||
rate = context.getString(paceId, getAnnounceTime(context, time));
|
||||
}
|
||||
|
||||
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
||||
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
|
||||
|
||||
return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(context, trackStatistics.getMovingTime()), rate);
|
||||
}
|
||||
|
||||
private static String getAnnounceTime(Context context, long time) {
|
||||
int[] parts = StringUtils.getTimeParts(time);
|
||||
String seconds = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceSeconds, parts[0], parts[0]);
|
||||
String minutes = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceMinutes, parts[1], parts[1]);
|
||||
String hours = context.getResources()
|
||||
.getQuantityString(R.plurals.voiceHours, parts[2], parts[2]);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (parts[2] != 0) {
|
||||
sb.append(hours);
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(minutes);
|
||||
sb.append(" ");
|
||||
sb.append(seconds);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plural count to be used by getQuantityString.
|
||||
* getQuantityString only supports integer quantities, not a double quantity like "2.2".
|
||||
* <p>
|
||||
* As a temporary workaround, we convert a double quantity to an integer quantity.
|
||||
* If the double quantity is exactly 0, 1, or 2, then we can return these integer quantities.
|
||||
* Otherwise, we cast the double quantity to an integer quantity.
|
||||
* However, we need to make sure that if the casted value is 0, 1, or 2, we don't return those, instead, return the next biggest integer 3.
|
||||
*
|
||||
* @param d the double value
|
||||
*/
|
||||
private static int getQuantityCount(double d) {
|
||||
if (d == 0) {
|
||||
return 0;
|
||||
} else if (d == 1) {
|
||||
return 1;
|
||||
} else if (d == 2) {
|
||||
return 2;
|
||||
} else {
|
||||
//TODO This seems weird; why not use Math.round(d) or Math.ceil()?
|
||||
int count = (int) d;
|
||||
return Math.max(count, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,20 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
|
||||
@@ -31,6 +40,8 @@ import de.dennisguse.opentracks.BuildConfig;
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
private static final String TAG = FileUtils.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Used to transfer picture from the camera.
|
||||
*/
|
||||
@@ -38,6 +49,8 @@ public class FileUtils {
|
||||
|
||||
public static final String EXPORT_DIR = "OpenTracks";
|
||||
|
||||
private static final String JPEG_EXTENSION = "jpeg";
|
||||
|
||||
/**
|
||||
* The maximum FAT32 path length. See the FAT32 spec at
|
||||
* http://msdn.microsoft.com/en-us/windows/hardware/gg463080
|
||||
@@ -196,4 +209,45 @@ public class FileUtils {
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image's absolute path from a track identified by trackId.
|
||||
*
|
||||
* @param context the context.
|
||||
* @param trackId the track id.
|
||||
*/
|
||||
public static String getImageUrl(Context context, long trackId) {
|
||||
File dir = FileUtils.getPhotoDir(context, trackId);
|
||||
|
||||
String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date());
|
||||
File file = new File(dir, FileUtils.buildUniqueFileName(dir, fileName, JPEG_EXTENSION));
|
||||
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a File (src) to a File (dst).
|
||||
*
|
||||
* @param src source file.
|
||||
* @param dst destination file.
|
||||
*/
|
||||
public static void copy(FileDescriptor src, File dst) {
|
||||
try (FileChannel in = new FileInputStream(src).getChannel();
|
||||
FileChannel out = new FileOutputStream(dst).getChannel()) {
|
||||
in.transferTo(0, in.size(), out);
|
||||
} catch (Exception e) {
|
||||
// post to log
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Uri for the file.
|
||||
*
|
||||
* @param context the context.
|
||||
* @param file the file.
|
||||
*/
|
||||
public static Uri getUriForFile(Context context, File file) {
|
||||
return FileProvider.getUriForFile(context, FileUtils.FILEPROVIDER, file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,9 +75,12 @@ public class IntentUtils {
|
||||
if (trackIds.length == 0) {
|
||||
throw new RuntimeException("Need to share at least one track.");
|
||||
}
|
||||
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
String trackDescription = "";
|
||||
if (trackIds.length == 1) {
|
||||
Track track = new ContentProviderUtils(context).getTrack(trackIds[0]);
|
||||
Track track = contentProviderUtils.getTrack(trackIds[0]);
|
||||
trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false);
|
||||
}
|
||||
|
||||
@@ -86,7 +89,12 @@ public class IntentUtils {
|
||||
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
for (long trackId : trackIds) {
|
||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.e(TAG, "TrackId " + trackId + " could not be resolved.");
|
||||
}
|
||||
|
||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, track.getName(), TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES);
|
||||
uris.add(uriAndMime.first);
|
||||
mime = uriAndMime.second;
|
||||
}
|
||||
@@ -99,6 +107,30 @@ public class IntentUtils {
|
||||
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an intent to share a waypoint image with an app.
|
||||
*
|
||||
* @param context the context.
|
||||
* @param uri uri with the image to share.
|
||||
*/
|
||||
public static Intent newShareImageIntent(Context context, Uri uri) {
|
||||
String mime = context.getContentResolver().getType(uri);
|
||||
|
||||
/*
|
||||
* Because the #166 bug, when you import KMZ tracks then it creates file:/// from markers with photo.
|
||||
* The photos should be content:/// not file:/// because getType(uri) always returns null for file:///
|
||||
* In .setType, to avoid side effects because the #166 bug described above it checks if mime is null.
|
||||
* If it is then it hardcode "images/*".
|
||||
*/
|
||||
return new Intent(Intent.ACTION_SEND)
|
||||
.setType(mime != null ? mime : "image/*")
|
||||
.setAction(Intent.ACTION_SEND)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_image_subject))
|
||||
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_image_body));
|
||||
}
|
||||
|
||||
public static void showCoordinateOnMap(Context context, Waypoint waypoint) {
|
||||
showCoordinateOnMap(context, waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName());
|
||||
}
|
||||
@@ -128,7 +160,6 @@ public class IntentUtils {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Send intent to show tracks on a map (needs an another app).
|
||||
*
|
||||
@@ -147,7 +178,7 @@ public class IntentUtils {
|
||||
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
|
||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, TrackFileFormat.KMZ_WITH_TRACKDETAIL);
|
||||
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", TrackFileFormat.KMZ_WITH_TRACKDETAIL);
|
||||
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
|
||||
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
/**
|
||||
* Utility class for decimating tracks at a given level of precision.
|
||||
@@ -84,16 +85,16 @@ public class LocationUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimates the given locations for a given zoom level.
|
||||
* Decimates the given trackPoints for a given zoom level.
|
||||
* This uses a Douglas-Peucker decimation algorithm.
|
||||
*
|
||||
* @param tolerance in meters
|
||||
* @param locations input
|
||||
* @param trackPoints input
|
||||
* @param decimated output
|
||||
*/
|
||||
//TODO What was it used for? Sharing data with other apps?
|
||||
private static void decimate(double tolerance, List<Location> locations, List<Location> decimated) {
|
||||
final int n = locations.size();
|
||||
private static void decimate(double tolerance, List<TrackPoint> trackPoints, List<TrackPoint> decimated) {
|
||||
final int n = trackPoints.size();
|
||||
if (n < 1) {
|
||||
return;
|
||||
}
|
||||
@@ -114,8 +115,7 @@ public class LocationUtils {
|
||||
current = stack.pop();
|
||||
maxDist = 0;
|
||||
for (idx = current[0] + 1; idx < current[1]; ++idx) {
|
||||
dist = LocationUtils.distance(
|
||||
locations.get(idx), locations.get(current[0]), locations.get(current[1]));
|
||||
dist = LocationUtils.distance(trackPoints.get(idx).getLocation(), trackPoints.get(current[0]).getLocation(), trackPoints.get(current[1]).getLocation());
|
||||
if (dist > maxDist) {
|
||||
maxDist = dist;
|
||||
maxIdx = idx;
|
||||
@@ -134,7 +134,7 @@ public class LocationUtils {
|
||||
int i = 0;
|
||||
idx = 0;
|
||||
decimated.clear();
|
||||
for (Location l : locations) {
|
||||
for (TrackPoint l : trackPoints) {
|
||||
if (dists[idx] != 0) {
|
||||
decimated.add(l);
|
||||
i++;
|
||||
@@ -151,9 +151,9 @@ public class LocationUtils {
|
||||
* @param precision desired precision in meters
|
||||
*/
|
||||
public static void decimate(Track track, double precision) {
|
||||
ArrayList<Location> decimated = new ArrayList<>();
|
||||
decimate(precision, track.getLocations(), decimated);
|
||||
track.setLocations(decimated);
|
||||
ArrayList<TrackPoint> decimated = new ArrayList<>();
|
||||
decimate(precision, track.getTrackPoints(), decimated);
|
||||
track.setTrackPoints(decimated);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +164,7 @@ public class LocationUtils {
|
||||
* @param location the location to test
|
||||
* @return true if the location is a valid location.
|
||||
*/
|
||||
//TODO Still needed?
|
||||
//TODO Move to TrackPoint
|
||||
public static boolean isValidLocation(Location location) {
|
||||
return location != null
|
||||
&& Math.abs(location.getLatitude()) <= 90
|
||||
|
||||
@@ -93,7 +93,7 @@ public class PreferencesUtils {
|
||||
* @param keyId the key id
|
||||
* @param defaultValue the default value
|
||||
*/
|
||||
public static boolean getBoolean(Context context, int keyId, boolean defaultValue) {
|
||||
private static boolean getBoolean(Context context, int keyId, boolean defaultValue) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
|
||||
}
|
||||
@@ -119,7 +119,7 @@ public class PreferencesUtils {
|
||||
* @param keyId the key id
|
||||
* @param defaultValue the default value
|
||||
*/
|
||||
public static int getInt(Context context, int keyId, int defaultValue) {
|
||||
private static int getInt(Context context, int keyId, int defaultValue) {
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context);
|
||||
|
||||
try {
|
||||
@@ -215,7 +215,7 @@ public class PreferencesUtils {
|
||||
setBoolean(context, R.string.recording_track_paused_key, RECORDING_TRACK_PAUSED);
|
||||
}
|
||||
|
||||
public static String getBluetoothHeartRateSensorAddressNone(Context context) {
|
||||
private static String getBluetoothHeartRateSensorAddressNone(Context context) {
|
||||
return context.getString(R.string.sensor_type_value_none);
|
||||
}
|
||||
|
||||
@@ -241,9 +241,9 @@ public class PreferencesUtils {
|
||||
return getBoolean(context, R.string.stats_keep_screen_on_while_recording_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean isShowStatsGradeElevation(Context context) {
|
||||
final boolean STATS_SHOW_GRADE_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_grade_elevation_default);
|
||||
return getBoolean(context, R.string.stats_show_grade_elevation_key, STATS_SHOW_GRADE_ELEVATION);
|
||||
public static boolean isShowStatsElevation(Context context) {
|
||||
final boolean STATS_SHOW_ELEVATION = context.getResources().getBoolean(R.bool.stats_show_elevation_default);
|
||||
return getBoolean(context, R.string.stats_show_grade_elevation_key, STATS_SHOW_ELEVATION);
|
||||
}
|
||||
|
||||
public static boolean isStatsShowCoordinate(Context context) {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@android:color/white"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M3,4V1h2v3h3v2H5v3H3V6H0V4H3zM6,10V7h3V4h7l1.83,2H21c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H5c-1.1,0 -2,-0.9 -2,-2V10H6zM13,19c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5s-5,2.24 -5,5S10.24,19 13,19zM9.8,14c0,1.77 1.43,3.2 3.2,3.2s3.2,-1.43 3.2,-3.2s-1.43,-3.2 -3.2,-3.2S9.8,12.23 9.8,14z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@android:color/white"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19,7v2.99s-1.99,0.01 -2,0L17,7h-3s0.01,-1.99 0,-2h3L17,2h2v3h3v2h-3zM16,11L16,8h-3L13,5L5,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-8h-3zM5,19l3,-4 2,3 3,-4 4,5L5,19z"/>
|
||||
</vector>
|
||||