diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java index 29f82ff18..c8b8f04e5 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.MarkerColumns; @@ -111,13 +112,13 @@ public class CustomContentProviderUtilsTest { long lastPointId = initializeTrack(trackId, numPoints); contentProviderUtils.setDefaultCursorBatchSize(batchSize); List locations = new ArrayList<>(numPoints); - try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L)) { + try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) { while (it.hasNext()) { - TrackPoint loc = it.next(); - assertNotNull(loc); - locations.add(loc); + TrackPoint trackPoint = it.next(); + assertNotNull(trackPoint); + locations.add(trackPoint); // Make sure the IDs are returned in the right order. - assertEquals(lastPointId - numPoints + locations.size(), it.getTrackPointId()); + assertEquals(lastPointId - numPoints + locations.size(), trackPoint.getId().getId()); } assertEquals(numPoints, locations.size()); } @@ -145,10 +146,10 @@ public class CustomContentProviderUtilsTest { // Load all inserted trackPoints. long lastPointId = -1; int counter = 0; - try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, -1L)) { + try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(id, null)) { while (it.hasNext()) { - it.next(); - lastPointId = it.getTrackPointId(); + TrackPoint trackPoint = it.next(); + lastPointId = trackPoint.getId().getId(); counter++; } } @@ -765,9 +766,9 @@ public class CustomContentProviderUtilsTest { // when / then contentProviderUtils.bulkInsertTrackPoint(track.second, trackId); - assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000).getCount()); + assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount()); contentProviderUtils.bulkInsertTrackPoint(track.second.subList(0, 8), trackId); - assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000).getCount()); + assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, null, 1000).getCount()); } /** @@ -869,13 +870,12 @@ public class CustomContentProviderUtilsTest { Pair> track = TestDataUtil.createTrack(trackId, 10); contentProviderUtils.insertTrack(track.first); - long[] trackpointIds = new long[track.second.size()]; - for (int i = 0; i < trackpointIds.length; i++) { - trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second.get(i), track.first.getId())); - } + List trackpointIds = track.second.stream() + .map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.getId()))) + .map(TrackPoint.Id::new).collect(Collectors.toList()); // when - Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5); + Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds.get(8), 5); // then assertEquals(2, cursor.getCount()); @@ -888,21 +888,20 @@ public class CustomContentProviderUtilsTest { Pair> track = TestDataUtil.createTrack(trackId, 10); contentProviderUtils.insertTrack(track.first); - long[] trackpointIds = new long[track.second.size()]; - for (int i = 0; i < trackpointIds.length; i++) { - trackpointIds[i] = ContentUris.parseId(contentProviderUtils.insertTrackPoint(track.second.get(i), track.first.getId())); - } + List trackpointIds = track.second.stream() + .map(it -> ContentUris.parseId(contentProviderUtils.insertTrackPoint(it, track.first.getId()))) + .map(TrackPoint.Id::new).collect(Collectors.toList()); - long startTrackPointId = trackpointIds[0]; + TrackPoint.Id startTrackPointId = trackpointIds.get(0); // when - TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, startTrackPointId); + TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, trackpointIds.get(0)); // then - for (int i = 0; i < trackpointIds.length; i++) { + for (int i = 0; i < trackpointIds.size(); i++) { assertTrue(trackPointIterator.hasNext()); TrackPoint trackPoint = trackPointIterator.next(); - assertEquals(startTrackPointId + i, trackPointIterator.getTrackPointId()); + assertEquals(startTrackPointId.getId() + i, trackPoint.getId().getId()); checkLocation(i, trackPoint.getLocation()); } diff --git a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java index e2eb9ff4a..789faec5b 100644 --- a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java +++ b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java @@ -83,8 +83,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL // Track points sampling state private int numLoadedPoints; - private long firstSeenTrackPointId; - private long lastSeenTrackPointId; + private TrackPoint.Id firstSeenTrackPointId; + private TrackPoint.Id lastSeenTrackPointId; // Registered listeners private ContentObserver tracksTableObserver; @@ -351,36 +351,42 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL } int localNumLoadedTrackPoints = updateSamplingState ? numLoadedPoints : 0; - long localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : -1L; - long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L; - long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId; + TrackPoint.Id localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : null; + TrackPoint.Id localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : null; + TrackPoint.Id maxPointId = updateSamplingState ? null : lastSeenTrackPointId; if (selectedTrackId == null) { Log.w(TAG, "This should not happen, but it does"); //TODO return; } - long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId); + TrackPoint.Id lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId); int samplingFrequency = -1; boolean includeNextPoint = false; - try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenTrackPointIdId + 1)) { + + TrackPoint.Id next; + if (localLastSeenTrackPointIdId != null) { + next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB. + } + + try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, null)) { while (trackPointIterator.hasNext()) { TrackPoint trackPoint = trackPointIterator.next(); - long trackPointId = trackPointIterator.getTrackPointId(); + TrackPoint.Id trackPointId = trackPoint.getId(); // Stop if past the last wanted point - if (maxPointId != -1L && trackPointId > maxPointId) { + if (maxPointId != null && trackPointId.getId() > maxPointId.getId()) { break; } - if (localFirstSeenTrackPointId == -1) { + if (localFirstSeenTrackPointId == null) { localFirstSeenTrackPointId = trackPointId; } if (samplingFrequency == -1) { - long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenTrackPointId); + long numTotalPoints = Math.max(0L, lastTrackPointId.getId() - localFirstSeenTrackPointId.getId()); //TODO That is an assumption; should be derived from the DB. samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints); } @@ -421,8 +427,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL */ private void resetSamplingState() { numLoadedPoints = 0; - firstSeenTrackPointId = -1L; - lastSeenTrackPointId = -1L; + firstSeenTrackPointId = null; + lastSeenTrackPointId = null; } /** diff --git a/src/main/java/de/dennisguse/opentracks/content/data/Marker.java b/src/main/java/de/dennisguse/opentracks/content/data/Marker.java index d65f2dc35..198e80dff 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/Marker.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/Marker.java @@ -82,8 +82,8 @@ public final class Marker { /** * May be null if the it was not loaded from the database. */ - public @Nullable - Id getId() { + @Nullable + public Id getId() { return id; } @@ -123,8 +123,8 @@ public final class Marker { this.icon = icon; } - public @NonNull - Track.Id getTrackId() { + @NonNull + public Track.Id getTrackId() { return trackId; } @@ -144,8 +144,8 @@ public final class Marker { this.duration = duration; } - public @NonNull - Location getLocation() { + @NonNull + public Location getLocation() { return location; } diff --git a/src/main/java/de/dennisguse/opentracks/content/data/Track.java b/src/main/java/de/dennisguse/opentracks/content/data/Track.java index 3ff3d5f8b..194e98806 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/Track.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/Track.java @@ -52,8 +52,8 @@ public class Track { /** * May be null if the track was not loaded from the database. */ - public @Nullable - Id getId() { + @Nullable + public Id getId() { return id; } diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java index bfd7afa5e..ab5fbfc4c 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java @@ -17,16 +17,23 @@ package de.dennisguse.opentracks.content.data; import android.location.Location; import android.location.LocationManager; +import android.os.Parcel; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Objects; /** * This class extends the standard Android location with extra information. * * @author Sandor Dornbush */ +//TODO Merge constructors by use case; we have too many. public class TrackPoint { + private TrackPoint.Id id; + private final Location location; private Float heartRate_bpm = null; @@ -92,8 +99,20 @@ public class TrackPoint { return new TrackPoint(resume); } - public @NonNull - Location getLocation() { + /** + * May be null if the track was not loaded from the database. + */ + @Nullable + public TrackPoint.Id getId() { + return id; + } + + public void setId(TrackPoint.Id id) { + this.id = id; + } + + @Nullable + public Location getLocation() { return location; } @@ -251,4 +270,41 @@ public class TrackPoint { public String toString() { return "time=" + getTime() + ": lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy(); } + + public static class Id { + + private final long id; + + public Id(long id) { + this.id = id; + } + + protected Id(Parcel in) { + id = in.readLong(); + } + + //TOOD Limit visibility to TrackRecordingService / ContentProvider + public long getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TrackPoint.Id id1 = (TrackPoint.Id) o; + return id == id1.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @NonNull + @Override + public String toString() { + return String.valueOf(id); + } + } } diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java index c08fd3c6a..05bf64904 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java @@ -545,6 +545,9 @@ public class ContentProviderUtils { */ static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) { TrackPoint trackPoint = new TrackPoint(); + if (!cursor.isNull(indexes.longitudeIndex)) { + trackPoint.setId(new TrackPoint.Id(cursor.getInt(indexes.idIndex))); + } if (!cursor.isNull(indexes.longitudeIndex)) { trackPoint.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6); @@ -622,15 +625,15 @@ public class ContentProviderUtils { * @param trackId the track id */ @Deprecated - public long getLastTrackPointId(@NonNull Track.Id trackId) { + public TrackPoint.Id getLastTrackPointId(@NonNull Track.Id trackId) { String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)"; String[] selectionArgs = new String[]{Long.toString(trackId.getId())}; try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) { if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); + return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID))); } } - return -1L; + return null; } /** @@ -640,15 +643,15 @@ public class ContentProviderUtils { * @param location the location * @return trackPoint id if the location is in the track. -1L otherwise. */ - public long getTrackPointId(Track.Id trackId, Location location) { + public TrackPoint.Id getTrackPointId(Track.Id trackId, Location location) { String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)"; String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())}; try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) { if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); + return new TrackPoint.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID))); } } - return -1L; + return null; } /** @@ -667,12 +670,12 @@ public class ContentProviderUtils { * @param startTrackPointId the starting trackPoint id. `null` to ignore * @param maxLocations maximum number of locations to return. `null` for no limit */ - public Cursor getTrackPointCursor(Track.Id trackId, Long startTrackPointId, Integer maxLocations) { + public Cursor getTrackPointCursor(Track.Id trackId, TrackPoint.Id startTrackPointId, Integer maxLocations) { String selection; String[] selectionArgs; if (startTrackPointId != null) { selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + ">=?"; - selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId)}; + selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId.getId())}; } else { selection = TrackPointsColumns.TRACKID + "=?"; selectionArgs = new String[]{Long.toString(trackId.getId())}; @@ -765,7 +768,7 @@ public class ContentProviderUtils { * @param trackId the track id * @param startTrackPointId the starting trackPoint id. `null` to ignore */ - public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final Long startTrackPointId) { + public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final TrackPoint.Id startTrackPointId) { return new TrackPointIterator(this, trackId, startTrackPointId); } @@ -794,7 +797,7 @@ public class ContentProviderUtils { public List getTrackPoints(Track.Id trackId) { List trackPoints = null; - try (Cursor trackPointCursor = getTrackPointCursor(trackId, -1L, -1)) { + try (Cursor trackPointCursor = getTrackPointCursor(trackId, null, null)) { if (trackPointCursor != null) { trackPointCursor.moveToFirst(); trackPoints = new ArrayList<>(trackPointCursor.getCount()); diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/TrackPointIterator.java b/src/main/java/de/dennisguse/opentracks/content/provider/TrackPointIterator.java index cef7b89c0..a5dcd1e80 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/TrackPointIterator.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/TrackPointIterator.java @@ -19,11 +19,11 @@ public class TrackPointIterator implements Iterator, AutoCloseable { private final ContentProviderUtils contentProviderUtils; private final Track.Id trackId; private final CachedTrackPointsIndexes indexes; - private long lastTrackPointId = -1L; + private TrackPoint.Id lastTrackPointId = null; private Cursor cursor; - public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, Long startTrackPointId) { + public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, TrackPoint.Id startTrackPointId) { this.contentProviderUtils = contentProviderUtils; this.trackId = trackId; @@ -37,7 +37,7 @@ public class TrackPointIterator implements Iterator, AutoCloseable { * * @param trackPointId the starting track point id */ - private Cursor getCursor(long trackPointId) { + private Cursor getCursor(TrackPoint.Id trackPointId) { return contentProviderUtils.getTrackPointCursor(trackId, trackPointId, contentProviderUtils.getDefaultCursorBatchSize()); } @@ -45,17 +45,13 @@ public class TrackPointIterator implements Iterator, AutoCloseable { * Advances the cursor to the next batch. Returns true if successful. */ private boolean advanceCursorToNextBatch() { - long trackPointId = lastTrackPointId == -1L ? -1L : lastTrackPointId + 1; + TrackPoint.Id trackPointId = lastTrackPointId == null ? null : new TrackPoint.Id(lastTrackPointId.getId() + 1); Log.d(TAG, "Advancing track point id: " + trackPointId); cursor.close(); cursor = getCursor(trackPointId); return cursor != null; } - public long getTrackPointId() { - return lastTrackPointId; - } - @Override public boolean hasNext() { if (cursor == null) { @@ -83,7 +79,7 @@ public class TrackPointIterator implements Iterator, AutoCloseable { throw new NoSuchElementException(); } } - lastTrackPointId = cursor.getLong(indexes.idIndex); + lastTrackPointId = new TrackPoint.Id(cursor.getLong(indexes.idIndex)); return ContentProviderUtils.fillTrackPoint(cursor, indexes); } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/FileTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/FileTrackExporter.java index 8d38b2b7d..693931bbd 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/FileTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/FileTrackExporter.java @@ -126,7 +126,7 @@ public class FileTrackExporter implements TrackExporter { boolean isLastLocationValid = false; TrackPoint lastTrackPoint = null; - try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L)) { + try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) { while (trackPointIterator.hasNext()) { if (Thread.interrupted()) { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java index ac8ac4d0f..238edad5c 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KmlTrackWriter.java @@ -398,8 +398,8 @@ public class KmlTrackWriter implements TrackWriter { * @param location the location */ private float getHeading(Track.Id trackId, Location location) { - long trackPointId = contentProviderUtils.getTrackPointId(trackId, location); - if (trackPointId == -1L) { + TrackPoint.Id trackPointId = contentProviderUtils.getTrackPointId(trackId, location); + if (trackPointId == null) { return location.getBearing(); } TrackPoint viewLocation = contentProviderUtils.getLastValidTrackPoint(trackId); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java index e330c27a6..de571e285 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java @@ -160,7 +160,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track // TODO Should not be necessary anymore? TrackStatisticsUpdater markerTrackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms()); - try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L)) { + try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) { while (true) { if (marker == null) { diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 6e9581add..c2372ff70 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -352,7 +352,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms()); - try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L)) { + try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) { trackStatisticsUpdater.addTrackPoint(trackPointIterator, recordingDistanceInterval); } catch (RuntimeException e) { Log.e(TAG, "RuntimeException", e);