diff --git a/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtils.java index 15c8ebd80..5e71b5502 100644 --- a/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtils.java @@ -22,7 +22,6 @@ import android.content.Context; import android.database.Cursor; import android.location.Location; import android.net.Uri; -import android.util.Log; import androidx.annotation.VisibleForTesting; @@ -30,7 +29,6 @@ import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import de.dennisguse.opentracks.BuildConfig; import de.dennisguse.opentracks.android.ContentResolverWrapper; @@ -831,102 +829,45 @@ 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 LocationIterator#next()} may advance to the next DB record, and if so, the iterator calls {@link LocationFactory#createLocation()} and populates it with information retrieved from the record. - * When done with iteration, {@link LocationIterator#close()} must be called. + * Fills a track point from a cursor. * - * @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 locationFactory the location factory + * @param cursor the cursor pointing to a location. + * @param indexes the cached track points indexes + * @param location the track point */ - public LocationIterator getTrackPointLocationIterator(final long trackId, - final long startTrackPointId, final boolean descending, - final LocationFactory locationFactory) { - if (locationFactory == null) { - throw new IllegalArgumentException("locationFactory is null"); + static void fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes, Location location) { + location.reset(); + + if (!cursor.isNull(indexes.longitudeIndex)) { + location.setLongitude(((double) cursor.getInt(indexes.longitudeIndex)) / 1E6); } - return new LocationIterator() { - private long lastTrackPointId = -1L; - private Cursor cursor = getCursor(startTrackPointId); - private final CachedTrackPointsIndexes - indexes = cursor != null ? new CachedTrackPointsIndexes(cursor) - : null; + 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; - /** - * Gets the track point cursor. - * - * @param trackPointId the starting track point id - */ - private Cursor getCursor(long trackPointId) { - return getTrackPointCursor(trackId, trackPointId, defaultCursorBatchSize, descending); - } + 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); - /** - * Advances the cursor to the next batch. Returns true if successful. - */ - private boolean advanceCursorToNextBatch() { - long trackPointId = lastTrackPointId == -1L ? -1L - : lastTrackPointId + (descending ? -1 : 1); - Log.d(TAG, "Advancing track point id: " + trackPointId); - cursor.close(); - cursor = getCursor(trackPointId); - return cursor != null; - } - - @Override - public long getLocationId() { - return lastTrackPointId; - } - - @Override - public boolean hasNext() { - if (cursor == null) { - return false; - } - if (cursor.isAfterLast()) { - return false; - } - if (cursor.isLast()) { - if (cursor.getCount() != defaultCursorBatchSize) { - return false; - } - return advanceCursorToNextBatch() && !cursor.isAfterLast(); - } - return true; - } - - @Override - public Location next() { - if (cursor == null) { - throw new NoSuchElementException(); - } - if (!cursor.moveToNext()) { - if (!advanceCursorToNextBatch() || !cursor.moveToNext()) { - throw new NoSuchElementException(); - } - } - lastTrackPointId = cursor.getLong(indexes.idIndex); - Location location = locationFactory.createLocation(); - fillTrackPoint(cursor, indexes, location); - return location; - } - - @Override - public void close() { - if (cursor != null) { - cursor.close(); - cursor = null; - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; + sensorDataSetLocation.setSensorDataSet(new SensorDataSet(heartRate, cadence, power, SensorDataSet.DATA_UNAVAILABLE, location.getTime())); + } } /** @@ -984,45 +925,22 @@ public class ContentProviderUtils { } /** - * Fills a track point from a cursor. + * 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 LocationIterator#next()} may advance to the next DB record, and if so, the iterator calls {@link LocationFactory#createLocation()} and populates it with information retrieved from the record. + * When done with iteration, {@link LocationIterator#close()} must be called. * - * @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 + * @param startTrackPointId the starting track point id. -1L to ignore + * @param descending true to sort the result in descending order (latest location first) + * @param locationFactory the location factory */ - private 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())); + public LocationIterator getTrackPointLocationIterator(final long trackId, final long startTrackPointId, final boolean descending, final LocationFactory locationFactory) { + if (locationFactory == null) { + throw new IllegalArgumentException("locationFactory is null"); } + return new LocationIterator(this, trackId, startTrackPointId, descending, locationFactory); } private Location findTrackPointBy(String selection, String[] selectionArgs) { @@ -1046,6 +964,10 @@ public class ContentProviderUtils { return contentResolver.query(TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); } + int getDefaultCursorBatchSize() { + return defaultCursorBatchSize; + } + /** * Sets the default cursor batch size. For testing purpose. * @@ -1059,7 +981,7 @@ public class ContentProviderUtils { /** * A cache of track points indexes. */ - private static class CachedTrackPointsIndexes { + static class CachedTrackPointsIndexes { final int idIndex; final int longitudeIndex; final int latitudeIndex; diff --git a/src/main/java/de/dennisguse/opentracks/content/LocationIterator.java b/src/main/java/de/dennisguse/opentracks/content/LocationIterator.java index 2191a5baf..ad140ce41 100644 --- a/src/main/java/de/dennisguse/opentracks/content/LocationIterator.java +++ b/src/main/java/de/dennisguse/opentracks/content/LocationIterator.java @@ -2,21 +2,105 @@ package de.dennisguse.opentracks.content; import android.database.Cursor; import android.location.Location; +import android.util.Log; import java.util.Iterator; +import java.util.NoSuchElementException; /** * A lightweight wrapper around the original {@link Cursor} with a method to clean up. */ -public interface LocationIterator extends Iterator, AutoCloseable { +public class LocationIterator implements Iterator, AutoCloseable { + + private static final String TAG = LocationIterator.class.getSimpleName(); + + private final ContentProviderUtils contentProviderUtils; + private final long trackId; + private final boolean descending; + private final LocationFactory locationFactory; + private final ContentProviderUtils.CachedTrackPointsIndexes indexes; + private long lastTrackPointId = -1L; + private Cursor cursor; + + + public LocationIterator(ContentProviderUtils contentProviderUtils, long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory) { + this.contentProviderUtils = contentProviderUtils; + this.trackId = trackId; + this.descending = descending; + this.locationFactory = locationFactory; + + cursor = getCursor(startTrackPointId); + indexes = cursor != null ? new ContentProviderUtils.CachedTrackPointsIndexes(cursor) + : null; + } /** - * Gets the most recently retrieved track point id by {@link #next()}. + * Gets the track point cursor. + * + * @param trackPointId the starting track point id */ - long getLocationId(); + private Cursor getCursor(long trackPointId) { + return contentProviderUtils.getTrackPointCursor(trackId, trackPointId, contentProviderUtils.getDefaultCursorBatchSize(), descending); + } /** - * Closes the iterator. + * Advances the cursor to the next batch. Returns true if successful. */ - void close(); + private boolean advanceCursorToNextBatch() { + long trackPointId = lastTrackPointId == -1L ? -1L : lastTrackPointId + (descending ? -1 : 1); + Log.d(TAG, "Advancing track point id: " + trackPointId); + cursor.close(); + cursor = getCursor(trackPointId); + return cursor != null; + } + + public long getLocationId() { + return lastTrackPointId; + } + + @Override + public boolean hasNext() { + if (cursor == null) { + return false; + } + if (cursor.isAfterLast()) { + return false; + } + if (cursor.isLast()) { + if (cursor.getCount() != contentProviderUtils.getDefaultCursorBatchSize()) { + return false; + } + return advanceCursorToNextBatch() && !cursor.isAfterLast(); + } + return true; + } + + @Override + public Location next() { + if (cursor == null) { + throw new NoSuchElementException(); + } + if (!cursor.moveToNext()) { + if (!advanceCursorToNextBatch() || !cursor.moveToNext()) { + throw new NoSuchElementException(); + } + } + lastTrackPointId = cursor.getLong(indexes.idIndex); + Location location = locationFactory.createLocation(); + ContentProviderUtils.fillTrackPoint(cursor, indexes, location); + return location; + } + + @Override + public void close() { + if (cursor != null) { + cursor.close(); + cursor = null; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } } \ No newline at end of file