From d7526f93b8d0c0231ab4d6de784a115e427b00a0 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Mon, 25 Nov 2019 22:59:41 +0100 Subject: [PATCH] Cleanup: removed ContentProviderUtils interface. --- ...va => CustomContentProviderUtilsTest.java} | 98 +- .../opentracks/content/SearchEngineTest.java | 2 +- .../services/TrackRecordingServiceTest.java | 2 +- .../TrackRecordingServiceTestLooper.java | 2 +- .../opentracks/DeleteAsyncTask.java | 2 +- .../opentracks/MarkerDetailActivity.java | 2 +- .../opentracks/MarkerEditActivity.java | 6 +- .../opentracks/MarkerListActivity.java | 2 +- .../opentracks/SearchListActivity.java | 2 +- .../opentracks/TrackDetailActivity.java | 2 +- .../opentracks/TrackEditActivity.java | 2 +- .../opentracks/TrackListActivity.java | 2 +- .../content/ContentProviderUtils.java | 1103 ++++++++++++++-- .../content/ContentProviderUtilsImpl.java | 1116 ----------------- .../opentracks/content/LocationFactory.java | 20 + .../opentracks/content/LocationIterator.java | 22 + .../content/ShareContentProvider.java | 2 +- .../opentracks/content/TrackDataHub.java | 8 +- .../fragments/DeleteMarkerDialogFragment.java | 2 +- .../fragments/MarkerDetailFragment.java | 2 +- .../opentracks/io/file/TrackFileFormat.java | 20 +- .../io/file/exporter/ExportAsyncTask.java | 2 +- .../io/file/exporter/FileTrackExporter.java | 5 +- .../io/file/exporter/KmlTrackWriter.java | 2 +- .../importer/AbstractFileTrackImporter.java | 5 +- .../file/importer/GpxFileTrackImporter.java | 2 +- .../io/file/importer/ImportAsyncTask.java | 3 +- .../file/importer/KmlFileTrackImporter.java | 2 +- .../io/file/importer/KmzTrackImporter.java | 3 +- .../services/TrackRecordingService.java | 7 +- .../opentracks/util/IntentUtils.java | 2 +- 31 files changed, 1161 insertions(+), 1291 deletions(-) rename src/androidTest/java/de/dennisguse/opentracks/content/{CustomContentProviderUtilsImplTest.java => CustomContentProviderUtilsTest.java} (88%) delete mode 100644 src/main/java/de/dennisguse/opentracks/content/ContentProviderUtilsImpl.java create mode 100644 src/main/java/de/dennisguse/opentracks/content/LocationFactory.java create mode 100644 src/main/java/de/dennisguse/opentracks/content/LocationIterator.java diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/CustomContentProviderUtilsImplTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/CustomContentProviderUtilsTest.java similarity index 88% rename from src/androidTest/java/de/dennisguse/opentracks/content/CustomContentProviderUtilsImplTest.java rename to src/androidTest/java/de/dennisguse/opentracks/content/CustomContentProviderUtilsTest.java index 317677ff0..5ae2240ba 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/CustomContentProviderUtilsImplTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/CustomContentProviderUtilsTest.java @@ -35,21 +35,19 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import de.dennisguse.opentracks.content.ContentProviderUtils.LocationFactory; -import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator; import de.dennisguse.opentracks.content.Waypoint.WaypointType; import de.dennisguse.opentracks.stats.TripStatistics; import static org.mockito.Mockito.when; /** - * A unit test for {@link ContentProviderUtilsImpl}. + * A unit test for {@link ContentProviderUtils}. * * @author Bartlomiej Niechwiej * @author Youtao Liu */ @RunWith(MockitoJUnitRunner.class) -public class CustomContentProviderUtilsImplTest { +public class CustomContentProviderUtilsTest { private static final String NAME_PREFIX = "test name"; private static final String MOCK_DESC = "Mock Next Waypoint Desc!"; private static final String TEST_DESC = "Test Desc!"; @@ -69,13 +67,13 @@ public class CustomContentProviderUtilsImplTest { @Before public void setUp() { - providerUtils = ContentProviderUtils.Factory.get(context); + providerUtils = new ContentProviderUtils(context); providerUtils.deleteAllTracks(context); } @Test public void testLocationIterator_noPoints() { - testIterator(1, 0, 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(1, 0, 1, false, LocationFactory.DEFAULT_LOCATION_FACTORY); } @Test @@ -105,36 +103,36 @@ public class CustomContentProviderUtilsImplTest { @Test public void testLocationIterator_noBatchAscending() { - testIterator(1, 50, 100, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - testIterator(2, 50, 50, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(1, 50, 100, false, LocationFactory.DEFAULT_LOCATION_FACTORY); + testIterator(2, 50, 50, false, LocationFactory.DEFAULT_LOCATION_FACTORY); } @Test public void testLocationIterator_noBatchDescending() { - testIterator(1, 50, 100, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - testIterator(2, 50, 50, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(1, 50, 100, true, LocationFactory.DEFAULT_LOCATION_FACTORY); + testIterator(2, 50, 50, true, LocationFactory.DEFAULT_LOCATION_FACTORY); } @Test public void testLocationIterator_batchAscending() { - testIterator(1, 50, 11, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - testIterator(2, 50, 25, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(1, 50, 11, false, LocationFactory.DEFAULT_LOCATION_FACTORY); + testIterator(2, 50, 25, false, LocationFactory.DEFAULT_LOCATION_FACTORY); } @Test public void testLocationIterator_batchDescending() { - testIterator(1, 50, 11, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); - testIterator(2, 50, 25, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(1, 50, 11, true, LocationFactory.DEFAULT_LOCATION_FACTORY); + testIterator(2, 50, 25, true, LocationFactory.DEFAULT_LOCATION_FACTORY); } @Test public void testLocationIterator_largeTrack() { - testIterator(1, 20000, 2000, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + testIterator(1, 20000, 2000, false, LocationFactory.DEFAULT_LOCATION_FACTORY); } private List testIterator(long trackId, int numPoints, int batchSize, boolean descending, LocationFactory locationFactory) { long lastPointId = initializeTrack(trackId, numPoints); - ((ContentProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize); + ((ContentProviderUtils) providerUtils).setDefaultCursorBatchSize(batchSize); List locations = new ArrayList(numPoints); try (LocationIterator it = providerUtils.getTrackPointLocationIterator(trackId, -1L, descending, locationFactory)) { while (it.hasNext()) { @@ -173,7 +171,7 @@ public class CustomContentProviderUtilsImplTest { // Load all inserted locations. long lastPointId = -1; int counter = 0; - try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) { + try (LocationIterator it = providerUtils.getTrackPointLocationIterator(id, -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) { while (it.hasNext()) { it.next(); lastPointId = it.getLocationId(); @@ -189,7 +187,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#createTrack(Cursor)}. + * Tests the method {@link ContentProviderUtils#createTrack(Cursor)}. */ @Test public void testCreateTrack() { @@ -216,7 +214,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#deleteAllTracks(Context)} + * Tests the method {@link ContentProviderUtils#deleteAllTracks(Context)} */ @Test public void testDeleteAllTracks() { @@ -245,7 +243,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#deleteTrack(Context, long)}. + * Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}. */ @Test public void testDeleteTrack() { @@ -280,7 +278,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getAllTracks()} + * Tests the method {@link ContentProviderUtils#getAllTracks()} */ @Test public void testGetAllTracks() { @@ -293,7 +291,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getLastTrack()} + * Tests the method {@link ContentProviderUtils#getLastTrack()} */ @Test public void testGetLastTrack() { @@ -303,7 +301,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getTrack(long)} + * Tests the method {@link ContentProviderUtils#getTrack(long)} */ @Test public void testGetTrack() { @@ -313,7 +311,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#updateTrack(Track)} + * Tests the method {@link ContentProviderUtils#updateTrack(Track)} */ @Test public void testUpdateTrack() { @@ -330,7 +328,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#createContentValues(Waypoint)}. + * Tests the method {@link ContentProviderUtils#createContentValues(Waypoint)}. */ @Test public void testCreateContentValues_waypoint() { @@ -368,7 +366,7 @@ public class CustomContentProviderUtilsImplTest { waypoint.setLocation(location); providerUtils.insertWaypoint(waypoint); - ContentProviderUtilsImpl contentProviderUtils = new ContentProviderUtilsImpl(contentResolverMock); + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock); long waypointId = System.currentTimeMillis(); waypoint.setId(waypointId); @@ -381,7 +379,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#createWaypoint(Cursor)}. + * Tests the method {@link ContentProviderUtils#createWaypoint(Cursor)}. */ @Test public void testCreateWaypoint() { @@ -416,7 +414,7 @@ public class CustomContentProviderUtilsImplTest { /** * Tests the method - * {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)} + * {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)} * when there is only one waypoint in the track. */ @Test @@ -437,7 +435,7 @@ public class CustomContentProviderUtilsImplTest { @Override public String generateWaypointDescription(TripStatistics tripStatistics) { - return CustomContentProviderUtilsImplTest.MOCK_DESC; + return CustomContentProviderUtilsTest.MOCK_DESC; } @Override @@ -452,7 +450,7 @@ public class CustomContentProviderUtilsImplTest { /** * Tests the method - * {@link ContentProviderUtilsImpl#deleteWaypoint(Context, long, DescriptionGenerator)} + * {@link ContentProviderUtils#deleteWaypoint(Context, long, DescriptionGenerator)} * when there is more than one waypoint in the track. */ @Test @@ -495,7 +493,7 @@ public class CustomContentProviderUtilsImplTest { DescriptionGenerator descriptionGenerator = new DescriptionGenerator() { @Override public String generateWaypointDescription(TripStatistics tripStatistics) { - return CustomContentProviderUtilsImplTest.MOCK_DESC; + return CustomContentProviderUtilsTest.MOCK_DESC; } @Override @@ -508,12 +506,12 @@ public class CustomContentProviderUtilsImplTest { providerUtils.deleteWaypoint(context, waypoint1Id, descriptionGenerator); Assert.assertNull(providerUtils.getWaypoint(waypoint1Id)); - Assert.assertEquals(CustomContentProviderUtilsImplTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id) + Assert.assertEquals(CustomContentProviderUtilsTest.MOCK_DESC, providerUtils.getWaypoint(waypoint2Id) .getDescription()); } /** - * Tests the method {@link ContentProviderUtilsImpl#getFirstWaypointId(long)}. + * Tests the method {@link ContentProviderUtils#getFirstWaypointId(long)}. */ @Test public void testGetFirstWaypointId() { @@ -535,7 +533,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getNextWaypointNumber(long, WaypointType)}. + * Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long, WaypointType)}. */ @Test public void testGetNextWaypointNumber() { @@ -594,8 +592,8 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#insertWaypoint(Waypoint)} and - * {@link ContentProviderUtilsImpl#getWaypoint(long)}. + * Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and + * {@link ContentProviderUtils#getWaypoint(long)}. */ @Test public void testInsertAndGetWaypoint() { @@ -612,7 +610,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#updateWaypoint(Waypoint)}. + * Tests the method {@link ContentProviderUtils#updateWaypoint(Waypoint)}. */ @Test public void testUpdateWaypoint() { @@ -634,7 +632,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#bulkInsertTrackPoint(Location[], int, long)}. + * Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(Location[], int, long)}. */ @Test public void testBulkInsertTrackPoint() { @@ -650,7 +648,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#createTrackPoint(Cursor)}. + * Tests the method {@link ContentProviderUtils#createTrackPoint(Cursor)}. */ @Test public void testCreateTrackPoint() { @@ -710,7 +708,7 @@ public class CustomContentProviderUtilsImplTest { /** * Tests the method - * {@link ContentProviderUtilsImpl#insertTrackPoint(Location, long)}. + * {@link ContentProviderUtils#insertTrackPoint(Location, long)}. */ @Test public void testInsertTrackPoint() { @@ -724,7 +722,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getFirstTrackPointId(long)}. + * Tests the method {@link ContentProviderUtils#getFirstTrackPointId(long)}. */ @Test public void testGetFirstTrackPointId() { @@ -737,7 +735,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getLastTrackPointId(long)}. + * Tests the method {@link ContentProviderUtils#getLastTrackPointId(long)}. */ @Test public void testGetLastTrackPointId() { @@ -750,7 +748,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getLastValidTrackPoint(long)}. + * Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(long)}. */ @Test public void testGetLastValidTrackPoint() { @@ -764,7 +762,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in descending. + * Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in descending. */ @Test public void testGetTrackPointCursor_desc() { @@ -783,7 +781,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getTrackPointCursor(long, long, int, boolean)} in ascending. + * Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in ascending. */ @Test public void testGetTrackPointCursor_asc() { @@ -802,7 +800,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending. + * Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in descending. */ @Test public void testGetTrackPointLocationIterator_desc() { @@ -818,7 +816,7 @@ public class CustomContentProviderUtilsImplTest { long startTrackPointId = trackpointIds[9]; - LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, true, LocationFactory.DEFAULT_LOCATION_FACTORY); for (int i = 0; i < trackpointIds.length; i++) { Assert.assertTrue(locationIterator.hasNext()); Location location = locationIterator.next(); @@ -829,7 +827,7 @@ public class CustomContentProviderUtilsImplTest { } /** - * Tests the method {@link ContentProviderUtilsImpl#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending. + * Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean, LocationFactory)} in ascending. */ @Test public void testGetTrackPointLocationIterator_asc() { @@ -845,7 +843,7 @@ public class CustomContentProviderUtilsImplTest { long startTrackPointId = trackpointIds[0]; - LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY); + LocationIterator locationIterator = providerUtils.getTrackPointLocationIterator(trackId, startTrackPointId, false, LocationFactory.DEFAULT_LOCATION_FACTORY); for (int i = 0; i < trackpointIds.length; i++) { Assert.assertTrue(locationIterator.hasNext()); Location location = locationIterator.next(); @@ -892,7 +890,7 @@ public class CustomContentProviderUtilsImplTest { /** * Checks the value of a location. * - * @param i the index of this location which created in the method {@link CustomContentProviderUtilsImplTest#getTrack(long, int)} + * @param i the index of this location which created in the method {@link CustomContentProviderUtilsTest#getTrack(long, int)} * @param location the location to be checked */ private void checkLocation(int i, Location location) { diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java index 7351809ab..6a7cf4212 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/SearchEngineTest.java @@ -58,7 +58,7 @@ public class SearchEngineTest { @Before public void setUp() { - providerUtils = ContentProviderUtils.Factory.get(context); + providerUtils = new ContentProviderUtils(context); engine = new SearchEngine(providerUtils); } diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java index 4f63b1a42..0e7e6c423 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java @@ -89,7 +89,7 @@ public class TrackRecordingServiceTest { }; customContentProvider.attachInfo(context, null); - providerUtils = ContentProviderUtils.Factory.get(context); + providerUtils = new ContentProviderUtils(context); // Let's use default values. SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java index 7e27dd52d..325b50d64 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTestLooper.java @@ -68,7 +68,7 @@ public class TrackRecordingServiceTestLooper { }; customContentProvider.attachInfo(context, null); - providerUtils = ContentProviderUtils.Factory.get(context); + providerUtils = new ContentProviderUtils(context); // Let's use default values. SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context); diff --git a/src/main/java/de/dennisguse/opentracks/DeleteAsyncTask.java b/src/main/java/de/dennisguse/opentracks/DeleteAsyncTask.java index 941933302..4bcaed63f 100644 --- a/src/main/java/de/dennisguse/opentracks/DeleteAsyncTask.java +++ b/src/main/java/de/dennisguse/opentracks/DeleteAsyncTask.java @@ -69,7 +69,7 @@ public class DeleteAsyncTask extends AsyncTask { @Override protected Boolean doInBackground(Void... params) { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); if (trackIds.length == 1 && trackIds[0] == -1L) { contentProviderUtils.deleteAllTracks(context); diff --git a/src/main/java/de/dennisguse/opentracks/MarkerDetailActivity.java b/src/main/java/de/dennisguse/opentracks/MarkerDetailActivity.java index b35b40289..f4874700e 100644 --- a/src/main/java/de/dennisguse/opentracks/MarkerDetailActivity.java +++ b/src/main/java/de/dennisguse/opentracks/MarkerDetailActivity.java @@ -61,7 +61,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark return; } - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(this); + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this); Waypoint waypoint = contentProviderUtils.getWaypoint(markerId); markerIds = new ArrayList<>(); diff --git a/src/main/java/de/dennisguse/opentracks/MarkerEditActivity.java b/src/main/java/de/dennisguse/opentracks/MarkerEditActivity.java index 0b7ef193b..bded8463d 100644 --- a/src/main/java/de/dennisguse/opentracks/MarkerEditActivity.java +++ b/src/main/java/de/dennisguse/opentracks/MarkerEditActivity.java @@ -127,7 +127,7 @@ public class MarkerEditActivity extends AbstractActivity { if (newMarker) { statisticsSection.setVisibility(View.GONE); waypointSection.setVisibility(View.VISIBLE); - int nextWaypointNumber = trackId == -1L ? -1 : ContentProviderUtils.Factory.get(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT); + int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId, WaypointType.WAYPOINT); if (nextWaypointNumber == -1) { nextWaypointNumber = 0; } @@ -136,7 +136,7 @@ public class MarkerEditActivity extends AbstractActivity { waypointMarkerType.setText(""); waypointDescription.setText(""); } else { - waypoint = ContentProviderUtils.Factory.get(this).getWaypoint(markerId); + waypoint = new ContentProviderUtils(this).getWaypoint(markerId); if (waypoint == null) { Log.d(TAG, "waypoint is null"); finish(); @@ -182,6 +182,6 @@ public class MarkerEditActivity extends AbstractActivity { waypoint.setCategory(waypointMarkerType.getText().toString()); waypoint.setDescription(waypointDescription.getText().toString()); } - ContentProviderUtils.Factory.get(this).updateWaypoint(waypoint); + new ContentProviderUtils(this).updateWaypoint(waypoint); } } diff --git a/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java b/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java index dbe3a705c..55df1d129 100644 --- a/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/MarkerListActivity.java @@ -130,7 +130,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); - contentProviderUtils = ContentProviderUtils.Factory.get(this); + contentProviderUtils = new ContentProviderUtils(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this); long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); diff --git a/src/main/java/de/dennisguse/opentracks/SearchListActivity.java b/src/main/java/de/dennisguse/opentracks/SearchListActivity.java index 3b3f36761..a0950ad5b 100644 --- a/src/main/java/de/dennisguse/opentracks/SearchListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/SearchListActivity.java @@ -170,7 +170,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); - contentProviderUtils = ContentProviderUtils.Factory.get(this); + contentProviderUtils = new ContentProviderUtils(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this); trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null); searchEngine = new SearchEngine(contentProviderUtils); diff --git a/src/main/java/de/dennisguse/opentracks/TrackDetailActivity.java b/src/main/java/de/dennisguse/opentracks/TrackDetailActivity.java index 9ce9f9ab2..142c9771c 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackDetailActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackDetailActivity.java @@ -189,7 +189,7 @@ public class TrackDetailActivity extends AbstractListActivity implements ChooseA photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null; hasPhoto = savedInstanceState != null && savedInstanceState.getBoolean(HAS_PHOTO_KEY, false); - contentProviderUtils = ContentProviderUtils.Factory.get(this); + contentProviderUtils = new ContentProviderUtils(this); handleIntent(getIntent()); sharedPreferences = PreferencesUtils.getSharedPreferences(this); diff --git a/src/main/java/de/dennisguse/opentracks/TrackEditActivity.java b/src/main/java/de/dennisguse/opentracks/TrackEditActivity.java index fea40d4fd..0a73a22e4 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackEditActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackEditActivity.java @@ -73,7 +73,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit return; } - contentProviderUtils = ContentProviderUtils.Factory.get(this); + contentProviderUtils = new ContentProviderUtils(this); track = contentProviderUtils.getTrack(trackId); if (track == null) { Log.e(TAG, "No track for " + trackId); diff --git a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java index 306e38100..5d41f5b56 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java @@ -245,7 +245,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); - contentProviderUtils = ContentProviderUtils.Factory.get(this); + contentProviderUtils = new ContentProviderUtils(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this); trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, bindChangedCallback); diff --git a/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtils.java index 8d1f969ff..f22a14240 100644 --- a/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2010 Google Inc. + * Copyright 2008 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 @@ -16,49 +16,70 @@ package de.dennisguse.opentracks.content; +import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.location.Location; -import android.location.LocationManager; import android.net.Uri; +import android.util.Log; +import androidx.annotation.VisibleForTesting; + +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; +import de.dennisguse.opentracks.android.IContentResolver; import de.dennisguse.opentracks.content.Waypoint.WaypointType; +import de.dennisguse.opentracks.content.sensor.SensorDataSet; +import de.dennisguse.opentracks.stats.TripStatistics; +import de.dennisguse.opentracks.util.FileUtils; /** - * Utilities to access data from the app's content provider. + * {@link ContentProviderUtils} implementation. + * Allows to use {@link ContentResolver} and {@link android.content.ContentProvider} interchangeably via {@link IContentResolver}. * - * @author Rodrigo Damazio + * @author Leif Hendrik Wilden */ -public interface ContentProviderUtils { +public class ContentProviderUtils { + + private static final String TAG = ContentProviderUtils.class.getSimpleName(); + private static final int MAX_LATITUDE = 90000000; /** * Maximum number of waypoints that will be loaded at one time. */ - int MAX_LOADED_WAYPOINTS_POINTS = 10000; + public static int MAX_LOADED_WAYPOINTS_POINTS = 10000; /** * The authority (the first part of the URI) for the app's content provider. */ - String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content"; + static String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content"; /** * The base URI for the app's content provider. */ - String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE; + static String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE; - /** - * The default {@link LocationFactory} which creates a location each time. - */ - LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory() { - @Override - public Location createLocation() { - return new SensorDataSetLocation(LocationManager.GPS_PROVIDER); - } - }; + private final IContentResolver contentResolver; + private int defaultCursorBatchSize = 2000; + + public ContentProviderUtils(Context context) { + this(context.getContentResolver()); + } + + public ContentProviderUtils(ContentResolver contentResolver) { + this.contentResolver = new ContentResolverWrapper(contentResolver); + } + + public ContentProviderUtils(IContentResolver contentResolver) { + this.contentResolver = contentResolver; + } /** * Clears a track: removes waypoints and trackpoints. @@ -66,38 +87,205 @@ public interface ContentProviderUtils { * * @param trackId the track id */ - void clearTrack(Context context, long trackId); + public void clearTrack(Context context, long trackId) { + deleteTrackPointsAndWaypoints(context, trackId); + Track track = new Track(); + track.setId(trackId); + updateTrack(track); + } /** * Creates a {@link Track} from a cursor. * * @param cursor the cursor pointing to the track */ - Track createTrack(Cursor cursor); + public Track createTrack(Cursor cursor) { + int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID); + int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME); + int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION); + int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY); + int startIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTID); + int stopIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPID); + 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); + int minLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLAT); + int maxLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLAT); + int minLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLON); + int maxLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLON); + int maxSpeedIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED); + 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(); + if (!cursor.isNull(idIndex)) { + track.setId(cursor.getLong(idIndex)); + } + if (!cursor.isNull(nameIndex)) { + track.setName(cursor.getString(nameIndex)); + } + if (!cursor.isNull(descriptionIndex)) { + track.setDescription(cursor.getString(descriptionIndex)); + } + if (!cursor.isNull(categoryIndex)) { + track.setCategory(cursor.getString(categoryIndex)); + } + if (!cursor.isNull(startIdIndex)) { + track.setStartId(cursor.getLong(startIdIndex)); + } + if (!cursor.isNull(stopIdIndex)) { + track.setStopId(cursor.getLong(stopIdIndex)); + } + if (!cursor.isNull(startTimeIndex)) { + tripStatistics.setStartTime(cursor.getLong(startTimeIndex)); + } + if (!cursor.isNull(stopTimeIndex)) { + tripStatistics.setStopTime(cursor.getLong(stopTimeIndex)); + } + if (!cursor.isNull(numPointsIndex)) { + track.setNumberOfPoints(cursor.getInt(numPointsIndex)); + } + if (!cursor.isNull(totalDistanceIndex)) { + tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex)); + } + if (!cursor.isNull(totalTimeIndex)) { + tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex)); + } + if (!cursor.isNull(movingTimeIndex)) { + tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex)); + } + if (!cursor.isNull(minLatIndex) && !cursor.isNull(maxLatIndex) && !cursor.isNull(minLonIndex) && !cursor.isNull(maxLonIndex)) { + int bottom = cursor.getInt(minLatIndex); + int top = cursor.getInt(maxLatIndex); + int left = cursor.getInt(minLonIndex); + int right = cursor.getInt(maxLonIndex); + tripStatistics.setBounds(left, top, right, bottom); + } + if (!cursor.isNull(maxSpeedIndex)) { + tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex)); + } + if (!cursor.isNull(minElevationIndex)) { + tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex)); + } + if (!cursor.isNull(maxElevationIndex)) { + tripStatistics.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)); + } + if (!cursor.isNull(iconIndex)) { + track.setIcon(cursor.getString(iconIndex)); + } + return track; + } /** * Deletes all tracks (including waypoints and track points). */ - void deleteAllTracks(Context context); + public void deleteAllTracks(Context context) { + contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null); + contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null); + // Delete tracks last since it triggers a database vaccum call + contentResolver.delete(TracksColumns.CONTENT_URI, null, null); + + File dir = FileUtils.getPhotoDir(); + deleteDirectoryRecurse(context, dir); + } /** * Deletes a track. * * @param trackId the track id */ - void deleteTrack(Context context, long trackId); + public void deleteTrack(Context context, long trackId) { + deleteTrackPointsAndWaypoints(context, trackId); + + // Delete track last since it triggers a database vaccum call + contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", + new String[]{Long.toString(trackId)}); + } + + /** + * Deletes track points and waypoints of a track. Assumes + * {@link TracksColumns#STARTID}, {@link TracksColumns#STOPID}, and + * {@link TracksColumns#NUMPOINTS} will be updated by the caller. + * + * @param trackId the track id + */ + private void deleteTrackPointsAndWaypoints(Context context, long trackId) { + Track track = getTrack(trackId); + if (track != null) { + String where = TrackPointsColumns._ID + ">=? AND " + TrackPointsColumns._ID + "<=?"; + String[] selectionArgs = new String[]{ + Long.toString(track.getStartId()), Long.toString(track.getStopId())}; + contentResolver.delete(TrackPointsColumns.CONTENT_URI, where, selectionArgs); + } + contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", + new String[]{Long.toString(trackId)}); + deleteDirectoryRecurse(context, FileUtils.getPhotoDir(trackId)); + } + + /** + * Delete the directory recursively. + * + * @param dir the directory + */ + private void deleteDirectoryRecurse(Context context, File dir) { + if (FileUtils.isDirectory(dir)) { + for (File child : dir.listFiles()) { + deleteDirectoryRecurse(context, child); + } + } + if (dir.exists()) { + dir.delete(); + FileUtils.updateMediaScanner(context, Uri.fromFile(dir)); + } + } /** * Gets all the tracks. * If no track exists, an empty list is returned. * NOTE: the returned tracks do not have any track points attached. */ - List getAllTracks(); + public List getAllTracks() { + ArrayList tracks = new ArrayList<>(); + try (Cursor cursor = getTrackCursor(null, null, null, TracksColumns._ID)) { + if (cursor != null && cursor.moveToFirst()) { + tracks.ensureCapacity(cursor.getCount()); + do { + tracks.add(createTrack(cursor)); + } while (cursor.moveToNext()); + } + } + return tracks; + } /** * Gets the last track or null. */ - Track getLastTrack(); + public Track getLastTrack() { + try (Cursor cursor = getTrackCursor(null, null, null, TracksColumns.STARTTIME + " DESC")) { + // Using the same order as shown in the track list + if (cursor != null && cursor.moveToNext()) { + return createTrack(cursor); + } + } + return null; + } /** * Gets a track by a track id or null @@ -105,7 +293,18 @@ public interface ContentProviderUtils { * * @param trackId the track id. */ - Track getTrack(long trackId); + public Track getTrack(long trackId) { + if (trackId < 0) { + return null; + } + try (Cursor cursor = getTrackCursor(null, TracksColumns._ID + "=?", + new String[]{Long.toString(trackId)}, TracksColumns._ID)) { + if (cursor != null && cursor.moveToNext()) { + return createTrack(cursor); + } + } + return null; + } /** * Gets a track cursor. @@ -115,7 +314,9 @@ public interface ContentProviderUtils { * @param selectionArgs the selection arguments. Can be null * @param sortOrder the sort order. Can be null */ - Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder); + public Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder) { + return getTrackCursor(null, selection, selectionArgs, sortOrder); + } /** * Inserts a track. @@ -124,7 +325,9 @@ public interface ContentProviderUtils { * @param track the track * @return the content provider URI of the inserted track. */ - Uri insertTrack(Track track); + public Uri insertTrack(Track track) { + return contentResolver.insert(TracksColumns.CONTENT_URI, createContentValues(track)); + } /** * Updates a track. @@ -132,14 +335,204 @@ public interface ContentProviderUtils { * * @param track the track */ - void updateTrack(Track track); + public void updateTrack(Track track) { + contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), + TracksColumns._ID + "=?", new String[]{Long.toString(track.getId())}); + } + + private ContentValues createContentValues(Track track) { + ContentValues values = new ContentValues(); + TripStatistics tripStatistics = track.getTripStatistics(); + + // Value < 0 indicates no id is available + if (track.getId() >= 0) { + values.put(TracksColumns._ID, track.getId()); + } + values.put(TracksColumns.NAME, track.getName()); + values.put(TracksColumns.DESCRIPTION, track.getDescription()); + values.put(TracksColumns.CATEGORY, track.getCategory()); + values.put(TracksColumns.STARTID, track.getStartId()); + values.put(TracksColumns.STOPID, track.getStopId()); + 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.MINLAT, tripStatistics.getBottom()); + values.put(TracksColumns.MAXLAT, tripStatistics.getTop()); + values.put(TracksColumns.MINLON, tripStatistics.getLeft()); + values.put(TracksColumns.MAXLON, tripStatistics.getRight()); + 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.ICON, track.getIcon()); + + return values; + } + + /** + * Gets a track cursor. + * + * @param projection the projection + * @param selection the selection + * @param selectionArgs the selection arguments + * @param sortOrder the sort oder + */ + private Cursor getTrackCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return contentResolver.query(TracksColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); + } /** * Creates a waypoint from a cursor. * * @param cursor the cursor pointing to the waypoint */ - Waypoint createWaypoint(Cursor cursor); + public Waypoint createWaypoint(Cursor cursor) { + int idIndex = cursor.getColumnIndexOrThrow(WaypointsColumns._ID); + int nameIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.NAME); + int descriptionIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DESCRIPTION); + int categoryIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.CATEGORY); + int iconIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ICON); + int trackIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TRACKID); + int typeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TYPE); + int lengthIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LENGTH); + int durationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DURATION); + int startTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTTIME); + int startIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTID); + int stopIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STOPID); + int longitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LONGITUDE); + int latitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LATITUDE); + 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 totalDistanceIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALDISTANCE); + int totalTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALTIME); + int movingTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MOVINGTIME); + int maxSpeedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXSPEED); + int minElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINELEVATION); + int maxElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXELEVATION); + int elevationGainIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN); + int minGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE); + int maxGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE); + int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL); + + Waypoint waypoint = new Waypoint(); + + if (!cursor.isNull(idIndex)) { + waypoint.setId(cursor.getLong(idIndex)); + } + if (!cursor.isNull(nameIndex)) { + waypoint.setName(cursor.getString(nameIndex)); + } + if (!cursor.isNull(descriptionIndex)) { + waypoint.setDescription(cursor.getString(descriptionIndex)); + } + if (!cursor.isNull(categoryIndex)) { + waypoint.setCategory(cursor.getString(categoryIndex)); + } + if (!cursor.isNull(iconIndex)) { + waypoint.setIcon(cursor.getString(iconIndex)); + } + if (!cursor.isNull(trackIdIndex)) { + waypoint.setTrackId(cursor.getLong(trackIdIndex)); + } + if (!cursor.isNull(typeIndex)) { + waypoint.setType(WaypointType.values()[cursor.getInt(typeIndex)]); + } + if (!cursor.isNull(lengthIndex)) { + waypoint.setLength(cursor.getFloat(lengthIndex)); + } + if (!cursor.isNull(durationIndex)) { + waypoint.setDuration(cursor.getLong(durationIndex)); + } + if (!cursor.isNull(startIdIndex)) { + waypoint.setStartId(cursor.getLong(startIdIndex)); + } + if (!cursor.isNull(stopIdIndex)) { + waypoint.setStopId(cursor.getLong(stopIdIndex)); + } + + 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); + + TripStatistics tripStatistics = new TripStatistics(); + boolean hasTripStatistics = false; + if (!cursor.isNull(startTimeIndex)) { + tripStatistics.setStartTime(cursor.getLong(startTimeIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(totalDistanceIndex)) { + tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(totalTimeIndex)) { + tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(movingTimeIndex)) { + tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(maxSpeedIndex)) { + tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(minElevationIndex)) { + tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(maxElevationIndex)) { + tripStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(elevationGainIndex)) { + tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(minGradeIndex)) { + tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex)); + hasTripStatistics = true; + } + if (!cursor.isNull(maxGradeIndex)) { + tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex)); + hasTripStatistics = true; + } + if (hasTripStatistics) { + waypoint.setTripStatistics(tripStatistics); + } + + if (!cursor.isNull(photoUrlIndex)) { + waypoint.setPhotoUrl(cursor.getString(photoUrlIndex)); + } + return waypoint; + } /** * Deletes a waypoint. @@ -149,7 +542,37 @@ public interface ContentProviderUtils { * @param waypointId the waypoint id * @param descriptionGenerator the description generator. Can be null for waypoint marker */ - void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator); + + public void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator) { + final Waypoint waypoint = getWaypoint(waypointId); + if (waypoint != null && waypoint.getType() == WaypointType.STATISTICS + && descriptionGenerator != null) { + final Waypoint nextWaypoint = getNextStatisticsWaypointAfter(waypoint); + if (nextWaypoint == null) { + Log.d(TAG, "Unable to find the next statistics marker after deleting one."); + } else { + nextWaypoint.getTripStatistics().merge(waypoint.getTripStatistics()); + nextWaypoint.setDescription( + descriptionGenerator.generateWaypointDescription(nextWaypoint.getTripStatistics())); + if (!updateWaypoint(nextWaypoint)) { + Log.e(TAG, "Unable to update the next statistics marker after deleting one."); + } + } + } + if (waypoint != null && waypoint.hasPhoto()) { + Uri uri = waypoint.getPhotoURI(); + File file = new File(uri.getPath()); + if (file.exists()) { + File parent = file.getParentFile(); + file.delete(); + FileUtils.updateMediaScanner(context, uri); + if (parent.listFiles().length == 0) { + parent.delete(); + } + } + } + contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)}); + } /** * Gets the first waypoint id for a track. @@ -158,7 +581,19 @@ public interface ContentProviderUtils { * * @param trackId the track id */ - long getFirstWaypointId(long trackId); + public long getFirstWaypointId(long trackId) { + if (trackId < 0) { + return -1L; + } + try (Cursor cursor = getWaypointCursor(new String[]{WaypointsColumns._ID}, + WaypointsColumns.TRACKID + "=?", new String[]{Long.toString(trackId)}, + WaypointsColumns._ID, 1)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndexOrThrow(WaypointsColumns._ID)); + } + } + return -1L; + } /** * Gets the last waypoint for a type. Returns null if it doesn't exist. @@ -166,7 +601,26 @@ public interface ContentProviderUtils { * @param trackId the track id * @param waypointType the waypoint type */ - Waypoint getLastWaypoint(long trackId, WaypointType waypointType); + public Waypoint getLastWaypoint(long trackId, WaypointType waypointType) { + if (trackId < 0) { + return null; + } + Cursor cursor = null; + try { + String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; + String[] selectionArgs = new String[]{ + Long.toString(trackId), Integer.toString(waypointType.ordinal())}; + cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID + " DESC", 1); + if (cursor != null && cursor.moveToFirst()) { + return createWaypoint(cursor); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } /** * Gets the next waypoint number for a type. @@ -175,7 +629,32 @@ public interface ContentProviderUtils { * @param trackId the track id * @param waypointType the waypoint type */ - int getNextWaypointNumber(long trackId, WaypointType waypointType); + public int getNextWaypointNumber(long trackId, WaypointType waypointType) { + if (trackId < 0) { + return -1; + } + Cursor cursor = null; + try { + String[] projection = {WaypointsColumns._ID}; + String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; + String[] selectionArgs = new String[]{ + Long.toString(trackId), Integer.toString(waypointType.ordinal())}; + cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1); + if (cursor != null) { + int count = cursor.getCount(); + /* + * For statistics markers, the first marker is for the track statistics, + * so return the count as the next user visible number. + */ + return waypointType == WaypointType.STATISTICS ? count : count + 1; + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return -1; + } /** * Gets a waypoint from a waypoint id. @@ -183,7 +662,18 @@ public interface ContentProviderUtils { * * @param waypointId the waypoint id */ - Waypoint getWaypoint(long waypointId); + public Waypoint getWaypoint(long waypointId) { + if (waypointId < 0) { + return null; + } + try (Cursor cursor = getWaypointCursor(null, WaypointsColumns._ID + "=?", + new String[]{Long.toString(waypointId)}, WaypointsColumns._ID, 1)) { + if (cursor != null && cursor.moveToFirst()) { + return createWaypoint(cursor); + } + } + return null; + } /** * Gets a waypoint cursor. @@ -195,7 +685,10 @@ public interface ContentProviderUtils { * @param maxWaypoints the maximum number of waypoints to return. -1 for no * limit */ - Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints); + public Cursor getWaypointCursor( + String selection, String[] selectionArgs, String sortOrder, int maxWaypoints) { + return getWaypointCursor(null, selection, selectionArgs, sortOrder, maxWaypoints); + } /** * Gets a waypoint cursor for a track. @@ -205,14 +698,44 @@ public interface ContentProviderUtils { * @param minWaypointId the minimum waypoint id. -1L to ignore * @param maxWaypoints the maximum number of waypoints to return. -1 for no limit */ - Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints); + public Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints) { + if (trackId < 0) { + return null; + } + + String selection; + String[] selectionArgs; + if (minWaypointId >= 0) { + selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + ">=?"; + selectionArgs = new String[]{Long.toString(trackId), Long.toString(minWaypointId)}; + } else { + selection = WaypointsColumns.TRACKID + "=?"; + selectionArgs = new String[]{Long.toString(trackId)}; + } + return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints); + } /** * Gets the number of waypoints for a track. * * @param trackId the track id */ - int getWaypointCount(long trackId); + public int getWaypointCount(long trackId) { + if (trackId < 0) { + return 0; + } + + String[] projection = new String[]{"count(*) AS count"}; + String selection = WaypointsColumns.TRACKID + "=?"; + String[] selectionArgs = new String[]{Long.toString(trackId)}; + Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID); + + cursor.moveToFirst(); + int count = cursor.getInt(0); + cursor.close(); + // not count the first waypoint + return count > 0 ? count - 1 : 0; + } /** * Inserts a waypoint. @@ -220,7 +743,10 @@ public interface ContentProviderUtils { * @param waypoint the waypoint * @return the content provider URI of the inserted waypoint. */ - Uri insertWaypoint(Waypoint waypoint); + public Uri insertWaypoint(Waypoint waypoint) { + waypoint.setId(-1L); + return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint)); + } /** * Updates a waypoint. @@ -228,7 +754,107 @@ public interface ContentProviderUtils { * * @param waypoint the waypoint */ - boolean updateWaypoint(Waypoint waypoint); + public boolean updateWaypoint(Waypoint waypoint) { + int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(waypoint), + WaypointsColumns._ID + "=?", new String[]{Long.toString(waypoint.getId())}); + return rows == 1; + } + + ContentValues createContentValues(Waypoint waypoint) { + ContentValues values = new ContentValues(); + + // Value < 0 indicates no id is available + if (waypoint.getId() >= 0) { + values.put(WaypointsColumns._ID, waypoint.getId()); + } + values.put(WaypointsColumns.NAME, waypoint.getName()); + values.put(WaypointsColumns.DESCRIPTION, waypoint.getDescription()); + values.put(WaypointsColumns.CATEGORY, waypoint.getCategory()); + values.put(WaypointsColumns.ICON, waypoint.getIcon()); + values.put(WaypointsColumns.TRACKID, waypoint.getTrackId()); + values.put(WaypointsColumns.TYPE, waypoint.getType().ordinal()); + values.put(WaypointsColumns.LENGTH, waypoint.getLength()); + values.put(WaypointsColumns.DURATION, waypoint.getDuration()); + values.put(WaypointsColumns.STARTID, waypoint.getStartId()); + values.put(WaypointsColumns.STOPID, waypoint.getStopId()); + + Location location = waypoint.getLocation(); + if (location != null) { + values.put(WaypointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6)); + values.put(WaypointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6)); + values.put(WaypointsColumns.TIME, location.getTime()); + if (location.hasAltitude()) { + values.put(WaypointsColumns.ALTITUDE, location.getAltitude()); + } + 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()); + } + } + + TripStatistics tripStatistics = waypoint.getTripStatistics(); + if (tripStatistics != null) { + values.put(WaypointsColumns.STARTTIME, tripStatistics.getStartTime()); + values.put(WaypointsColumns.TOTALDISTANCE, tripStatistics.getTotalDistance()); + values.put(WaypointsColumns.TOTALTIME, tripStatistics.getTotalTime()); + values.put(WaypointsColumns.MOVINGTIME, tripStatistics.getMovingTime()); + values.put(WaypointsColumns.AVGSPEED, tripStatistics.getAverageSpeed()); + values.put(WaypointsColumns.AVGMOVINGSPEED, tripStatistics.getAverageMovingSpeed()); + values.put(WaypointsColumns.MAXSPEED, tripStatistics.getMaxSpeed()); + values.put(WaypointsColumns.MINELEVATION, tripStatistics.getMinElevation()); + values.put(WaypointsColumns.MAXELEVATION, tripStatistics.getMaxElevation()); + values.put(WaypointsColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain()); + values.put(WaypointsColumns.MINGRADE, tripStatistics.getMinGrade()); + values.put(WaypointsColumns.MAXGRADE, tripStatistics.getMaxGrade()); + } + + values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl()); + return values; + } + + private Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint) { + Cursor cursor = null; + try { + String selection = WaypointsColumns._ID + ">? AND " + WaypointsColumns.TRACKID + "=? AND " + + WaypointsColumns.TYPE + "=" + WaypointType.STATISTICS.ordinal(); + String[] selectionArgs = new String[]{ + Long.toString(waypoint.getId()), Long.toString(waypoint.getTrackId())}; + cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, 1); + if (cursor != null && cursor.moveToFirst()) { + return createWaypoint(cursor); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + /** + * Gets a waypoint cursor. + * + * @param projection the projection + * @param selection the selection + * @param selectionArgs the selection args + * @param sortOrder the sort order + * @param maxWaypoints the maximum number of waypoints + */ + private Cursor getWaypointCursor(String[] projection, String selection, String[] selectionArgs, + String sortOrder, int maxWaypoints) { + if (sortOrder == null) { + sortOrder = WaypointsColumns._ID; + } + if (maxWaypoints >= 0) { + sortOrder += " LIMIT " + maxWaypoints; + } + return contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); + } /** * Inserts multiple track points. @@ -239,14 +865,27 @@ public interface ContentProviderUtils { * @param trackId the track id * @return the number of points inserted */ - int bulkInsertTrackPoint(Location[] locations, int length, long trackId); + public int bulkInsertTrackPoint(Location[] locations, int length, long trackId) { + if (length == -1) { + length = locations.length; + } + ContentValues[] values = new ContentValues[length]; + for (int i = 0; i < length; i++) { + values[i] = createContentValues(locations[i], trackId); + } + return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values); + } /** * Creates a location object from a cursor. * * @param cursor the cursor pointing to the location */ - Location createTrackPoint(Cursor cursor); + public Location createTrackPoint(Cursor cursor) { + Location location = new SensorDataSetLocation(""); + fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor), location); + return location; + } /** * Gets the first location id for a track. @@ -254,7 +893,28 @@ public interface ContentProviderUtils { * * @param trackId the track id */ - long getFirstTrackPointId(long trackId); + public long getFirstTrackPointId(long trackId) { + if (trackId < 0) { + return -1L; + } + Cursor cursor = null; + try { + String selection = TrackPointsColumns._ID + "=(select min(" + TrackPointsColumns._ID + + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + + "=?)"; + String[] selectionArgs = new String[]{Long.toString(trackId)}; + cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, + selectionArgs, TrackPointsColumns._ID); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return -1L; + } /** * Gets the last location id for a track. @@ -262,7 +922,28 @@ public interface ContentProviderUtils { * * @param trackId the track id */ - long getLastTrackPointId(long trackId); + public long getLastTrackPointId(long trackId) { + if (trackId < 0) { + return -1L; + } + Cursor cursor = null; + try { + String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID + + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + + "=?)"; + String[] selectionArgs = new String[]{Long.toString(trackId)}; + cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, + selectionArgs, TrackPointsColumns._ID); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return -1L; + } /** * Gets the track point id of a location. @@ -271,7 +952,29 @@ public interface ContentProviderUtils { * @param location the location * @return track point id if the location is in the track. -1L otherwise. */ - long getTrackPointId(long trackId, Location location); + public long getTrackPointId(long trackId, Location location) { + if (trackId < 0) { + return -1L; + } + Cursor cursor = null; + try { + 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), Long.toString(location.getTime())}; + cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, + selectionArgs, TrackPointsColumns._ID); + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return -1L; + } /** * Gets the first valid location for a track. @@ -279,7 +982,16 @@ public interface ContentProviderUtils { * * @param trackId the track id */ - Location getFirstValidTrackPoint(long trackId); + public Location getFirstValidTrackPoint(long trackId) { + if (trackId < 0) { + return null; + } + String selection = TrackPointsColumns._ID + "=(select min(" + 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); + } /** * Gets the last valid location for a track. @@ -287,7 +999,16 @@ public interface ContentProviderUtils { * * @param trackId the track id */ - Location getLastValidTrackPoint(long trackId); + 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); + } /** * Creates a location cursor. The caller owns the returned cursor and is responsible for closing it. @@ -297,7 +1018,33 @@ public interface ContentProviderUtils { * @param maxLocations maximum number of locations to return. -1 for no limit * @param descending true to sort the result in descending order (latest location first) */ - Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending); + public Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending) { + if (trackId < 0) { + return null; + } + + String selection; + String[] selectionArgs; + if (startTrackPointId >= 0) { + String comparison = descending ? "<=" : ">="; + selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison + + "?"; + selectionArgs = new String[]{Long.toString(trackId), Long.toString(startTrackPointId)}; + } else { + selection = TrackPointsColumns.TRACKID + "=?"; + selectionArgs = new String[]{Long.toString(trackId)}; + } + + String sortOrder = TrackPointsColumns._ID; + if (descending) { + sortOrder += " DESC"; + } + if (maxLocations >= 0) { + sortOrder += " LIMIT " + maxLocations; + } + return getTrackPointCursor(null, selection, selectionArgs, sortOrder); + } + /** * Creates a new read-only iterator over a given track's points. @@ -311,7 +1058,92 @@ public interface ContentProviderUtils { * @param descending true to sort the result in descending order (latest location first) * @param locationFactory the location factory */ - LocationIterator getTrackPointLocationIterator(long trackId, long startTrackPointId, boolean descending, LocationFactory locationFactory); + 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() { + private long lastTrackPointId = -1L; + private Cursor cursor = getCursor(startTrackPointId); + private final CachedTrackPointsIndexes + indexes = cursor != null ? new CachedTrackPointsIndexes(cursor) + : null; + + /** + * Gets the track point cursor. + * + * @param trackPointId the starting track point id + */ + private Cursor getCursor(long trackPointId) { + return getTrackPointCursor(trackId, trackPointId, defaultCursorBatchSize, descending); + } + + /** + * 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(); + } + }; + } /** * Inserts a track point. @@ -320,48 +1152,159 @@ public interface ContentProviderUtils { * @param trackId the track id * @return the content provider URI of the inserted track point */ - Uri insertTrackPoint(Location location, long trackId); - - /** - * A lightweight wrapper around the original {@link Cursor} with a method to clean up. - */ - interface LocationIterator extends Iterator, AutoCloseable { - - /** - * Gets the most recently retrieved track point id by {@link #next()}. - */ - long getLocationId(); - - /** - * Closes the iterator. - */ - void close(); + public Uri insertTrackPoint(Location location, long trackId) { + return contentResolver.insert(TrackPointsColumns.CONTENT_URI, createContentValues(location, trackId)); } /** - * A factory for creating new {@link Location}. + * Creates the {@link ContentValues} for a {@link Location}. + * + * @param location the location + * @param trackId the track id */ - interface LocationFactory { + private ContentValues createContentValues(Location location, 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)); - /** - * Creates a new {@link Location}. - * An implementation can create new instances or reuse existing instances for optimization. - */ - Location createLocation(); + // Hack for Samsung phones that don't properly populate the time field + long time = location.getTime(); + if (time == 0) { + time = System.currentTimeMillis(); + } + values.put(TrackPointsColumns.TIME, time); + if (location.hasAltitude()) { + values.put(TrackPointsColumns.ALTITUDE, location.getAltitude()); + } + if (location.hasAccuracy()) { + values.put(TrackPointsColumns.ACCURACY, location.getAccuracy()); + } + if (location.hasSpeed()) { + values.put(TrackPointsColumns.SPEED, location.getSpeed()); + } + if (location.hasBearing()) { + values.put(TrackPointsColumns.BEARING, location.getBearing()); + } + + //SensorData + if (location instanceof SensorDataSetLocation) { + SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) 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()); + } + } + return values; } /** - * A factory which can produce instances of {@link ContentProviderUtils}, and can be overridden for testing. + * Fills a track point from a cursor. + * + * @param cursor the cursor pointing to a location. + * @param indexes the cached track points indexes + * @param location the track point */ - class Factory { + private void fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes, Location location) { + location.reset(); - /** - * Creates an instance of {@link ContentProviderUtils}. - * - * @param context the context - */ - public static ContentProviderUtils get(Context context) { - return new ContentProviderUtilsImpl(context.getContentResolver()); + 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 SensorDataSetLocation) { + SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) 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())); } } -} + + private Location findTrackPointBy(String selection, String[] selectionArgs) { + try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) { + if (cursor != null && cursor.moveToNext()) { + return createTrackPoint(cursor); + } + } + return null; + } + + /** + * Gets a track point cursor. + * + * @param projection the projection + * @param selection the selection + * @param selectionArgs the selection arguments + * @param sortOrder the sort order + */ + private Cursor getTrackPointCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return contentResolver.query(TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); + } + + /** + * Sets the default cursor batch size. For testing purpose. + * + * @param defaultCursorBatchSize the default cursor batch size + */ + @VisibleForTesting + void setDefaultCursorBatchSize(int defaultCursorBatchSize) { + this.defaultCursorBatchSize = defaultCursorBatchSize; + } + + /** + * A cache of track points indexes. + */ + private static class CachedTrackPointsIndexes { + final int idIndex; + final int longitudeIndex; + final int latitudeIndex; + final int timeIndex; + final int altitudeIndex; + final int accuracyIndex; + final int speedIndex; + final int bearingIndex; + final int sensorHeartRateIndex; + final int sensorCadenceIndex; + final int sensorPowerIndex; + + CachedTrackPointsIndexes(Cursor cursor) { + idIndex = cursor.getColumnIndex(TrackPointsColumns._ID); + longitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE); + latitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE); + timeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME); + altitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE); + accuracyIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY); + speedIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED); + bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING); + sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE); + sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE); + sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtilsImpl.java b/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtilsImpl.java deleted file mode 100644 index 72f640a4a..000000000 --- a/src/main/java/de/dennisguse/opentracks/content/ContentProviderUtilsImpl.java +++ /dev/null @@ -1,1116 +0,0 @@ -/* - * Copyright 2008 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.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.location.Location; -import android.net.Uri; -import android.util.Log; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; - -import de.dennisguse.opentracks.android.ContentResolverWrapper; -import de.dennisguse.opentracks.android.IContentResolver; -import de.dennisguse.opentracks.content.Waypoint.WaypointType; -import de.dennisguse.opentracks.content.sensor.SensorDataSet; -import de.dennisguse.opentracks.stats.TripStatistics; -import de.dennisguse.opentracks.util.FileUtils; - -/** - * {@link ContentProviderUtils} implementation. - * Allows to use {@link ContentResolver} and {@link android.content.ContentProvider} interchangeably via {@link IContentResolver}. - * - * @author Leif Hendrik Wilden - */ -public class ContentProviderUtilsImpl implements ContentProviderUtils { - - private static final String TAG = ContentProviderUtilsImpl.class.getSimpleName(); - - private static final int MAX_LATITUDE = 90000000; - - private final IContentResolver contentResolver; - private int defaultCursorBatchSize = 2000; - - public ContentProviderUtilsImpl(ContentResolver contentResolver) { - this.contentResolver = new ContentResolverWrapper(contentResolver); - } - - public ContentProviderUtilsImpl(IContentResolver contentResolver) { - this.contentResolver = contentResolver; - } - - @Override - public void clearTrack(Context context, long trackId) { - deleteTrackPointsAndWaypoints(context, trackId); - Track track = new Track(); - track.setId(trackId); - updateTrack(track); - } - - @Override - public Track createTrack(Cursor cursor) { - int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID); - int nameIndex = cursor.getColumnIndexOrThrow(TracksColumns.NAME); - int descriptionIndex = cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION); - int categoryIndex = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY); - int startIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTID); - int stopIdIndex = cursor.getColumnIndexOrThrow(TracksColumns.STOPID); - 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); - int minLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLAT); - int maxLatIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLAT); - int minLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MINLON); - int maxLonIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXLON); - int maxSpeedIndex = cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED); - 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(); - if (!cursor.isNull(idIndex)) { - track.setId(cursor.getLong(idIndex)); - } - if (!cursor.isNull(nameIndex)) { - track.setName(cursor.getString(nameIndex)); - } - if (!cursor.isNull(descriptionIndex)) { - track.setDescription(cursor.getString(descriptionIndex)); - } - if (!cursor.isNull(categoryIndex)) { - track.setCategory(cursor.getString(categoryIndex)); - } - if (!cursor.isNull(startIdIndex)) { - track.setStartId(cursor.getLong(startIdIndex)); - } - if (!cursor.isNull(stopIdIndex)) { - track.setStopId(cursor.getLong(stopIdIndex)); - } - if (!cursor.isNull(startTimeIndex)) { - tripStatistics.setStartTime(cursor.getLong(startTimeIndex)); - } - if (!cursor.isNull(stopTimeIndex)) { - tripStatistics.setStopTime(cursor.getLong(stopTimeIndex)); - } - if (!cursor.isNull(numPointsIndex)) { - track.setNumberOfPoints(cursor.getInt(numPointsIndex)); - } - if (!cursor.isNull(totalDistanceIndex)) { - tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex)); - } - if (!cursor.isNull(totalTimeIndex)) { - tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex)); - } - if (!cursor.isNull(movingTimeIndex)) { - tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex)); - } - if (!cursor.isNull(minLatIndex) && !cursor.isNull(maxLatIndex) && !cursor.isNull(minLonIndex) && !cursor.isNull(maxLonIndex)) { - int bottom = cursor.getInt(minLatIndex); - int top = cursor.getInt(maxLatIndex); - int left = cursor.getInt(minLonIndex); - int right = cursor.getInt(maxLonIndex); - tripStatistics.setBounds(left, top, right, bottom); - } - if (!cursor.isNull(maxSpeedIndex)) { - tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex)); - } - if (!cursor.isNull(minElevationIndex)) { - tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex)); - } - if (!cursor.isNull(maxElevationIndex)) { - tripStatistics.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)); - } - if (!cursor.isNull(iconIndex)) { - track.setIcon(cursor.getString(iconIndex)); - } - return track; - } - - @Override - public void deleteAllTracks(Context context) { - contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null); - contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null); - // Delete tracks last since it triggers a database vaccum call - contentResolver.delete(TracksColumns.CONTENT_URI, null, null); - - File dir = FileUtils.getPhotoDir(); - deleteDirectoryRecurse(context, dir); - } - - @Override - public void deleteTrack(Context context, long trackId) { - deleteTrackPointsAndWaypoints(context, trackId); - - // Delete track last since it triggers a database vaccum call - contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", - new String[]{Long.toString(trackId)}); - } - - /** - * Deletes track points and waypoints of a track. Assumes - * {@link TracksColumns#STARTID}, {@link TracksColumns#STOPID}, and - * {@link TracksColumns#NUMPOINTS} will be updated by the caller. - * - * @param trackId the track id - */ - private void deleteTrackPointsAndWaypoints(Context context, long trackId) { - Track track = getTrack(trackId); - if (track != null) { - String where = TrackPointsColumns._ID + ">=? AND " + TrackPointsColumns._ID + "<=?"; - String[] selectionArgs = new String[]{ - Long.toString(track.getStartId()), Long.toString(track.getStopId())}; - contentResolver.delete(TrackPointsColumns.CONTENT_URI, where, selectionArgs); - } - contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", - new String[]{Long.toString(trackId)}); - deleteDirectoryRecurse(context, FileUtils.getPhotoDir(trackId)); - } - - /** - * Delete the directory recursively. - * - * @param dir the directory - */ - private void deleteDirectoryRecurse(Context context, File dir) { - if (FileUtils.isDirectory(dir)) { - for (File child : dir.listFiles()) { - deleteDirectoryRecurse(context, child); - } - } - if (dir.exists()) { - dir.delete(); - FileUtils.updateMediaScanner(context, Uri.fromFile(dir)); - } - } - - @Override - public List getAllTracks() { - ArrayList tracks = new ArrayList<>(); - try (Cursor cursor = getTrackCursor(null, null, null, TracksColumns._ID)) { - if (cursor != null && cursor.moveToFirst()) { - tracks.ensureCapacity(cursor.getCount()); - do { - tracks.add(createTrack(cursor)); - } while (cursor.moveToNext()); - } - } - return tracks; - } - - @Override - public Track getLastTrack() { - try (Cursor cursor = getTrackCursor(null, null, null, TracksColumns.STARTTIME + " DESC")) { - // Using the same order as shown in the track list - if (cursor != null && cursor.moveToNext()) { - return createTrack(cursor); - } - } - return null; - } - - @Override - public Track getTrack(long trackId) { - if (trackId < 0) { - return null; - } - try (Cursor cursor = getTrackCursor(null, TracksColumns._ID + "=?", - new String[]{Long.toString(trackId)}, TracksColumns._ID)) { - if (cursor != null && cursor.moveToNext()) { - return createTrack(cursor); - } - } - return null; - } - - @Override - public Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder) { - return getTrackCursor(null, selection, selectionArgs, sortOrder); - } - - @Override - public Uri insertTrack(Track track) { - return contentResolver.insert(TracksColumns.CONTENT_URI, createContentValues(track)); - } - - @Override - public void updateTrack(Track track) { - contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), - TracksColumns._ID + "=?", new String[]{Long.toString(track.getId())}); - } - - private ContentValues createContentValues(Track track) { - ContentValues values = new ContentValues(); - TripStatistics tripStatistics = track.getTripStatistics(); - - // Value < 0 indicates no id is available - if (track.getId() >= 0) { - values.put(TracksColumns._ID, track.getId()); - } - values.put(TracksColumns.NAME, track.getName()); - values.put(TracksColumns.DESCRIPTION, track.getDescription()); - values.put(TracksColumns.CATEGORY, track.getCategory()); - values.put(TracksColumns.STARTID, track.getStartId()); - values.put(TracksColumns.STOPID, track.getStopId()); - 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.MINLAT, tripStatistics.getBottom()); - values.put(TracksColumns.MAXLAT, tripStatistics.getTop()); - values.put(TracksColumns.MINLON, tripStatistics.getLeft()); - values.put(TracksColumns.MAXLON, tripStatistics.getRight()); - 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.ICON, track.getIcon()); - - return values; - } - - /** - * Gets a track cursor. - * - * @param projection the projection - * @param selection the selection - * @param selectionArgs the selection arguments - * @param sortOrder the sort oder - */ - private Cursor getTrackCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) { - return contentResolver.query(TracksColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); - } - - @Override - public Waypoint createWaypoint(Cursor cursor) { - int idIndex = cursor.getColumnIndexOrThrow(WaypointsColumns._ID); - int nameIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.NAME); - int descriptionIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DESCRIPTION); - int categoryIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.CATEGORY); - int iconIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ICON); - int trackIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TRACKID); - int typeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TYPE); - int lengthIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LENGTH); - int durationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DURATION); - int startTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTTIME); - int startIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTID); - int stopIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.STOPID); - int longitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LONGITUDE); - int latitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LATITUDE); - 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 totalDistanceIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALDISTANCE); - int totalTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALTIME); - int movingTimeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MOVINGTIME); - int maxSpeedIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXSPEED); - int minElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINELEVATION); - int maxElevationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXELEVATION); - int elevationGainIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN); - int minGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE); - int maxGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE); - int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL); - - Waypoint waypoint = new Waypoint(); - - if (!cursor.isNull(idIndex)) { - waypoint.setId(cursor.getLong(idIndex)); - } - if (!cursor.isNull(nameIndex)) { - waypoint.setName(cursor.getString(nameIndex)); - } - if (!cursor.isNull(descriptionIndex)) { - waypoint.setDescription(cursor.getString(descriptionIndex)); - } - if (!cursor.isNull(categoryIndex)) { - waypoint.setCategory(cursor.getString(categoryIndex)); - } - if (!cursor.isNull(iconIndex)) { - waypoint.setIcon(cursor.getString(iconIndex)); - } - if (!cursor.isNull(trackIdIndex)) { - waypoint.setTrackId(cursor.getLong(trackIdIndex)); - } - if (!cursor.isNull(typeIndex)) { - waypoint.setType(WaypointType.values()[cursor.getInt(typeIndex)]); - } - if (!cursor.isNull(lengthIndex)) { - waypoint.setLength(cursor.getFloat(lengthIndex)); - } - if (!cursor.isNull(durationIndex)) { - waypoint.setDuration(cursor.getLong(durationIndex)); - } - if (!cursor.isNull(startIdIndex)) { - waypoint.setStartId(cursor.getLong(startIdIndex)); - } - if (!cursor.isNull(stopIdIndex)) { - waypoint.setStopId(cursor.getLong(stopIdIndex)); - } - - 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); - - TripStatistics tripStatistics = new TripStatistics(); - boolean hasTripStatistics = false; - if (!cursor.isNull(startTimeIndex)) { - tripStatistics.setStartTime(cursor.getLong(startTimeIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(totalDistanceIndex)) { - tripStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(totalTimeIndex)) { - tripStatistics.setTotalTime(cursor.getLong(totalTimeIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(movingTimeIndex)) { - tripStatistics.setMovingTime(cursor.getLong(movingTimeIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(maxSpeedIndex)) { - tripStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(minElevationIndex)) { - tripStatistics.setMinElevation(cursor.getFloat(minElevationIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(maxElevationIndex)) { - tripStatistics.setMaxElevation(cursor.getFloat(maxElevationIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(elevationGainIndex)) { - tripStatistics.setTotalElevationGain(cursor.getFloat(elevationGainIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(minGradeIndex)) { - tripStatistics.setMinGrade(cursor.getFloat(minGradeIndex)); - hasTripStatistics = true; - } - if (!cursor.isNull(maxGradeIndex)) { - tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex)); - hasTripStatistics = true; - } - if (hasTripStatistics) { - waypoint.setTripStatistics(tripStatistics); - } - - if (!cursor.isNull(photoUrlIndex)) { - waypoint.setPhotoUrl(cursor.getString(photoUrlIndex)); - } - return waypoint; - } - - @Override - public void deleteWaypoint(Context context, long waypointId, DescriptionGenerator descriptionGenerator) { - final Waypoint waypoint = getWaypoint(waypointId); - if (waypoint != null && waypoint.getType() == WaypointType.STATISTICS - && descriptionGenerator != null) { - final Waypoint nextWaypoint = getNextStatisticsWaypointAfter(waypoint); - if (nextWaypoint == null) { - Log.d(TAG, "Unable to find the next statistics marker after deleting one."); - } else { - nextWaypoint.getTripStatistics().merge(waypoint.getTripStatistics()); - nextWaypoint.setDescription( - descriptionGenerator.generateWaypointDescription(nextWaypoint.getTripStatistics())); - if (!updateWaypoint(nextWaypoint)) { - Log.e(TAG, "Unable to update the next statistics marker after deleting one."); - } - } - } - if (waypoint != null && waypoint.hasPhoto()) { - Uri uri = waypoint.getPhotoURI(); - File file = new File(uri.getPath()); - if (file.exists()) { - File parent = file.getParentFile(); - file.delete(); - FileUtils.updateMediaScanner(context, uri); - if (parent.listFiles().length == 0) { - parent.delete(); - } - } - } - contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)}); - } - - @Override - public long getFirstWaypointId(long trackId) { - if (trackId < 0) { - return -1L; - } - try (Cursor cursor = getWaypointCursor(new String[]{WaypointsColumns._ID}, - WaypointsColumns.TRACKID + "=?", new String[]{Long.toString(trackId)}, - WaypointsColumns._ID, 1)) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(WaypointsColumns._ID)); - } - } - return -1L; - } - - @Override - public Waypoint getLastWaypoint(long trackId, WaypointType waypointType) { - if (trackId < 0) { - return null; - } - Cursor cursor = null; - try { - String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; - String[] selectionArgs = new String[]{ - Long.toString(trackId), Integer.toString(waypointType.ordinal())}; - cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID + " DESC", 1); - if (cursor != null && cursor.moveToFirst()) { - return createWaypoint(cursor); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - @Override - public int getNextWaypointNumber(long trackId, WaypointType waypointType) { - if (trackId < 0) { - return -1; - } - Cursor cursor = null; - try { - String[] projection = {WaypointsColumns._ID}; - String selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns.TYPE + "=?"; - String[] selectionArgs = new String[]{ - Long.toString(trackId), Integer.toString(waypointType.ordinal())}; - cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1); - if (cursor != null) { - int count = cursor.getCount(); - /* - * For statistics markers, the first marker is for the track statistics, - * so return the count as the next user visible number. - */ - return waypointType == WaypointType.STATISTICS ? count : count + 1; - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return -1; - } - - @Override - public Waypoint getWaypoint(long waypointId) { - if (waypointId < 0) { - return null; - } - try (Cursor cursor = getWaypointCursor(null, WaypointsColumns._ID + "=?", - new String[]{Long.toString(waypointId)}, WaypointsColumns._ID, 1)) { - if (cursor != null && cursor.moveToFirst()) { - return createWaypoint(cursor); - } - } - return null; - } - - @Override - public Cursor getWaypointCursor( - String selection, String[] selectionArgs, String sortOrder, int maxWaypoints) { - return getWaypointCursor(null, selection, selectionArgs, sortOrder, maxWaypoints); - } - - @Override - public Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints) { - if (trackId < 0) { - return null; - } - - String selection; - String[] selectionArgs; - if (minWaypointId >= 0) { - selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + ">=?"; - selectionArgs = new String[]{Long.toString(trackId), Long.toString(minWaypointId)}; - } else { - selection = WaypointsColumns.TRACKID + "=?"; - selectionArgs = new String[]{Long.toString(trackId)}; - } - return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints); - } - - @Override - public int getWaypointCount(long trackId) { - if (trackId < 0) { - return 0; - } - - String[] projection = new String[]{"count(*) AS count"}; - String selection = WaypointsColumns.TRACKID + "=?"; - String[] selectionArgs = new String[]{Long.toString(trackId)}; - Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID); - - cursor.moveToFirst(); - int count = cursor.getInt(0); - cursor.close(); - // not count the first waypoint - return count > 0 ? count - 1 : 0; - } - - @Override - public Uri insertWaypoint(Waypoint waypoint) { - waypoint.setId(-1L); - return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint)); - } - - @Override - public boolean updateWaypoint(Waypoint waypoint) { - int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(waypoint), - WaypointsColumns._ID + "=?", new String[]{Long.toString(waypoint.getId())}); - return rows == 1; - } - - ContentValues createContentValues(Waypoint waypoint) { - ContentValues values = new ContentValues(); - - // Value < 0 indicates no id is available - if (waypoint.getId() >= 0) { - values.put(WaypointsColumns._ID, waypoint.getId()); - } - values.put(WaypointsColumns.NAME, waypoint.getName()); - values.put(WaypointsColumns.DESCRIPTION, waypoint.getDescription()); - values.put(WaypointsColumns.CATEGORY, waypoint.getCategory()); - values.put(WaypointsColumns.ICON, waypoint.getIcon()); - values.put(WaypointsColumns.TRACKID, waypoint.getTrackId()); - values.put(WaypointsColumns.TYPE, waypoint.getType().ordinal()); - values.put(WaypointsColumns.LENGTH, waypoint.getLength()); - values.put(WaypointsColumns.DURATION, waypoint.getDuration()); - values.put(WaypointsColumns.STARTID, waypoint.getStartId()); - values.put(WaypointsColumns.STOPID, waypoint.getStopId()); - - Location location = waypoint.getLocation(); - if (location != null) { - values.put(WaypointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6)); - values.put(WaypointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6)); - values.put(WaypointsColumns.TIME, location.getTime()); - if (location.hasAltitude()) { - values.put(WaypointsColumns.ALTITUDE, location.getAltitude()); - } - 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()); - } - } - - TripStatistics tripStatistics = waypoint.getTripStatistics(); - if (tripStatistics != null) { - values.put(WaypointsColumns.STARTTIME, tripStatistics.getStartTime()); - values.put(WaypointsColumns.TOTALDISTANCE, tripStatistics.getTotalDistance()); - values.put(WaypointsColumns.TOTALTIME, tripStatistics.getTotalTime()); - values.put(WaypointsColumns.MOVINGTIME, tripStatistics.getMovingTime()); - values.put(WaypointsColumns.AVGSPEED, tripStatistics.getAverageSpeed()); - values.put(WaypointsColumns.AVGMOVINGSPEED, tripStatistics.getAverageMovingSpeed()); - values.put(WaypointsColumns.MAXSPEED, tripStatistics.getMaxSpeed()); - values.put(WaypointsColumns.MINELEVATION, tripStatistics.getMinElevation()); - values.put(WaypointsColumns.MAXELEVATION, tripStatistics.getMaxElevation()); - values.put(WaypointsColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain()); - values.put(WaypointsColumns.MINGRADE, tripStatistics.getMinGrade()); - values.put(WaypointsColumns.MAXGRADE, tripStatistics.getMaxGrade()); - } - - values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl()); - return values; - } - - private Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint) { - Cursor cursor = null; - try { - String selection = WaypointsColumns._ID + ">? AND " + WaypointsColumns.TRACKID + "=? AND " - + WaypointsColumns.TYPE + "=" + WaypointType.STATISTICS.ordinal(); - String[] selectionArgs = new String[]{ - Long.toString(waypoint.getId()), Long.toString(waypoint.getTrackId())}; - cursor = getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, 1); - if (cursor != null && cursor.moveToFirst()) { - return createWaypoint(cursor); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - /** - * Gets a waypoint cursor. - * - * @param projection the projection - * @param selection the selection - * @param selectionArgs the selection args - * @param sortOrder the sort order - * @param maxWaypoints the maximum number of waypoints - */ - private Cursor getWaypointCursor(String[] projection, String selection, String[] selectionArgs, - String sortOrder, int maxWaypoints) { - if (sortOrder == null) { - sortOrder = WaypointsColumns._ID; - } - if (maxWaypoints >= 0) { - sortOrder += " LIMIT " + maxWaypoints; - } - return contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); - } - - @Override - public int bulkInsertTrackPoint(Location[] locations, int length, long trackId) { - if (length == -1) { - length = locations.length; - } - ContentValues[] values = new ContentValues[length]; - for (int i = 0; i < length; i++) { - values[i] = createContentValues(locations[i], trackId); - } - return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values); - } - - @Override - public Location createTrackPoint(Cursor cursor) { - Location location = new SensorDataSetLocation(""); - fillTrackPoint(cursor, new CachedTrackPointsIndexes(cursor), location); - return location; - } - - @Override - public long getFirstTrackPointId(long trackId) { - if (trackId < 0) { - return -1L; - } - Cursor cursor = null; - try { - String selection = TrackPointsColumns._ID + "=(select min(" + TrackPointsColumns._ID - + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID - + "=?)"; - String[] selectionArgs = new String[]{Long.toString(trackId)}; - cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, - selectionArgs, TrackPointsColumns._ID); - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return -1L; - } - - @Override - public long getLastTrackPointId(long trackId) { - if (trackId < 0) { - return -1L; - } - Cursor cursor = null; - try { - String selection = TrackPointsColumns._ID + "=(select max(" + TrackPointsColumns._ID - + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID - + "=?)"; - String[] selectionArgs = new String[]{Long.toString(trackId)}; - cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, - selectionArgs, TrackPointsColumns._ID); - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return -1L; - } - - @Override - public long getTrackPointId(long trackId, Location location) { - if (trackId < 0) { - return -1L; - } - Cursor cursor = null; - try { - 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), Long.toString(location.getTime())}; - cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, - selectionArgs, TrackPointsColumns._ID); - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return -1L; - } - - @Override - public Location getFirstValidTrackPoint(long trackId) { - if (trackId < 0) { - return null; - } - String selection = TrackPointsColumns._ID + "=(select min(" + 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); - } - - @Override - 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); - } - - @Override - public Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending) { - if (trackId < 0) { - return null; - } - - String selection; - String[] selectionArgs; - if (startTrackPointId >= 0) { - String comparison = descending ? "<=" : ">="; - selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison - + "?"; - selectionArgs = new String[]{Long.toString(trackId), Long.toString(startTrackPointId)}; - } else { - selection = TrackPointsColumns.TRACKID + "=?"; - selectionArgs = new String[]{Long.toString(trackId)}; - } - - String sortOrder = TrackPointsColumns._ID; - if (descending) { - sortOrder += " DESC"; - } - if (maxLocations >= 0) { - sortOrder += " LIMIT " + maxLocations; - } - return getTrackPointCursor(null, selection, selectionArgs, sortOrder); - } - - @Override - 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() { - private long lastTrackPointId = -1L; - private Cursor cursor = getCursor(startTrackPointId); - private final CachedTrackPointsIndexes - indexes = cursor != null ? new CachedTrackPointsIndexes(cursor) - : null; - - /** - * Gets the track point cursor. - * - * @param trackPointId the starting track point id - */ - private Cursor getCursor(long trackPointId) { - return getTrackPointCursor(trackId, trackPointId, defaultCursorBatchSize, descending); - } - - /** - * 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(); - } - }; - } - - @Override - public Uri insertTrackPoint(Location location, long trackId) { - return contentResolver.insert(TrackPointsColumns.CONTENT_URI, createContentValues(location, trackId)); - } - - /** - * Creates the {@link ContentValues} for a {@link Location}. - * - * @param location the location - * @param trackId the track id - */ - private ContentValues createContentValues(Location location, 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)); - - // Hack for Samsung phones that don't properly populate the time field - long time = location.getTime(); - if (time == 0) { - time = System.currentTimeMillis(); - } - values.put(TrackPointsColumns.TIME, time); - if (location.hasAltitude()) { - values.put(TrackPointsColumns.ALTITUDE, location.getAltitude()); - } - if (location.hasAccuracy()) { - values.put(TrackPointsColumns.ACCURACY, location.getAccuracy()); - } - if (location.hasSpeed()) { - values.put(TrackPointsColumns.SPEED, location.getSpeed()); - } - if (location.hasBearing()) { - values.put(TrackPointsColumns.BEARING, location.getBearing()); - } - - //SensorData - if (location instanceof SensorDataSetLocation) { - SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) 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()); - } - } - return values; - } - - /** - * Fills a track point from a cursor. - * - * @param cursor the cursor pointing to a location. - * @param indexes the cached track points indexes - * @param location the track point - */ - 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 SensorDataSetLocation) { - SensorDataSetLocation sensorDataSetLocation = (SensorDataSetLocation) 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())); - } - } - - private Location findTrackPointBy(String selection, String[] selectionArgs) { - try (Cursor cursor = getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns._ID)) { - if (cursor != null && cursor.moveToNext()) { - return createTrackPoint(cursor); - } - } - return null; - } - - /** - * Gets a track point cursor. - * - * @param projection the projection - * @param selection the selection - * @param selectionArgs the selection arguments - * @param sortOrder the sort order - */ - private Cursor getTrackPointCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) { - return contentResolver.query(TrackPointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder); - } - - /** - * Sets the default cursor batch size. For testing purpose. - * - * @param defaultCursorBatchSize the default cursor batch size - */ - void setDefaultCursorBatchSize(int defaultCursorBatchSize) { - this.defaultCursorBatchSize = defaultCursorBatchSize; - } - - /** - * A cache of track points indexes. - */ - private static class CachedTrackPointsIndexes { - final int idIndex; - final int longitudeIndex; - final int latitudeIndex; - final int timeIndex; - final int altitudeIndex; - final int accuracyIndex; - final int speedIndex; - final int bearingIndex; - final int sensorHeartRateIndex; - final int sensorCadenceIndex; - final int sensorPowerIndex; - - CachedTrackPointsIndexes(Cursor cursor) { - idIndex = cursor.getColumnIndex(TrackPointsColumns._ID); - longitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE); - latitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE); - timeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME); - altitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE); - accuracyIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY); - speedIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED); - bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING); - sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE); - sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE); - sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER); - } - } -} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/content/LocationFactory.java b/src/main/java/de/dennisguse/opentracks/content/LocationFactory.java new file mode 100644 index 000000000..a52b3e6da --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/content/LocationFactory.java @@ -0,0 +1,20 @@ +package de.dennisguse.opentracks.content; + +import android.location.Location; +import android.location.LocationManager; + +/** + * Creates a new {@link SensorDataSetLocation}. + * An implementation can create new instances or reuse existing instances for optimization. + */ +public class LocationFactory { + + /** + * The default {@link LocationFactory} which creates a location each time. + */ + public static LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory(); + + public Location createLocation() { + return new SensorDataSetLocation(LocationManager.GPS_PROVIDER); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/content/LocationIterator.java b/src/main/java/de/dennisguse/opentracks/content/LocationIterator.java new file mode 100644 index 000000000..2191a5baf --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/content/LocationIterator.java @@ -0,0 +1,22 @@ +package de.dennisguse.opentracks.content; + +import android.database.Cursor; +import android.location.Location; + +import java.util.Iterator; + +/** + * A lightweight wrapper around the original {@link Cursor} with a method to clean up. + */ +public interface LocationIterator extends Iterator, AutoCloseable { + + /** + * Gets the most recently retrieved track point id by {@link #next()}. + */ + long getLocationId(); + + /** + * Closes the iterator. + */ + void close(); +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/content/ShareContentProvider.java b/src/main/java/de/dennisguse/opentracks/content/ShareContentProvider.java index 61839845e..cc86b7a4f 100644 --- a/src/main/java/de/dennisguse/opentracks/content/ShareContentProvider.java +++ b/src/main/java/de/dennisguse/opentracks/content/ShareContentProvider.java @@ -198,7 +198,7 @@ public class ShareContentProvider extends CustomContentProvider implements ICont @Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { - ContentProviderUtils contentProviderUtils = new ContentProviderUtilsImpl(this); + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this); long[] trackIds = parseURI(uri); final Track[] tracks = new Track[trackIds.length]; diff --git a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java index 0e99ebaf7..e7aa86a68 100644 --- a/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java +++ b/src/main/java/de/dennisguse/opentracks/content/TrackDataHub.java @@ -30,7 +30,6 @@ import java.util.EnumSet; import java.util.Set; import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator; import de.dennisguse.opentracks.util.LocationUtils; import de.dennisguse.opentracks.util.PreferencesUtils; @@ -45,7 +44,7 @@ public class TrackDataHub implements DataSourceListener { * Target number of track points displayed by the map overlay. * We may display more than this number of points. */ - static final int TARGET_DISPLAYED_TRACK_POINTS = 5000; + private static final int TARGET_DISPLAYED_TRACK_POINTS = 5000; /** * Maximum number of waypoints to displayed. @@ -97,8 +96,7 @@ public class TrackDataHub implements DataSourceListener { } public synchronized static TrackDataHub newInstance(Context context) { - return new TrackDataHub(context, new TrackDataManager(), ContentProviderUtils.Factory.get( - context), TARGET_DISPLAYED_TRACK_POINTS); + return new TrackDataHub(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS); } public void start() { @@ -448,7 +446,7 @@ public class TrackDataHub implements DataSourceListener { int samplingFrequency = -1; boolean includeNextPoint = false; - try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) { + try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenLocationId + 1, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) { while (locationIterator.hasNext()) { Location location = locationIterator.next(); diff --git a/src/main/java/de/dennisguse/opentracks/fragments/DeleteMarkerDialogFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/DeleteMarkerDialogFragment.java index d8601f5a7..ec375d45c 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/DeleteMarkerDialogFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/DeleteMarkerDialogFragment.java @@ -81,7 +81,7 @@ public class DeleteMarkerDialogFragment extends DialogFragment { new Thread(new Runnable() { @Override public void run() { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(fragmentActivity); + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity); for (long markerId : markerIds) { contentProviderUtils.deleteWaypoint(fragmentActivity, markerId, new DescriptionGeneratorImpl(fragmentActivity)); diff --git a/src/main/java/de/dennisguse/opentracks/fragments/MarkerDetailFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/MarkerDetailFragment.java index 7fcd90261..119a9507c 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/MarkerDetailFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/MarkerDetailFragment.java @@ -110,7 +110,7 @@ public class MarkerDetailFragment extends Fragment { getFragmentManager().popBackStack(); return; } - contentProviderUtils = ContentProviderUtils.Factory.get(getActivity()); + contentProviderUtils = new ContentProviderUtils(getActivity()); handler = new Handler(); setHasOptionsMenu(true); } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/TrackFileFormat.java b/src/main/java/de/dennisguse/opentracks/io/file/TrackFileFormat.java index 1991328d1..c1bb4108d 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/TrackFileFormat.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/TrackFileFormat.java @@ -173,10 +173,12 @@ public enum TrackFileFormat { private static final String MIME_KML = "application/vnd.google-earth.kml+xml"; - public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); - TrackWriter trackWriter = this.newTrackWriter(context, tracks.length > 1); - return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener); + private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener, boolean exportPhotos) { + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); + + FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener); + + return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos); } /** @@ -184,12 +186,10 @@ public enum TrackFileFormat { */ public abstract String getMimeType(); - private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, TrackExporterListener trackExporterListener, boolean exportPhotos) { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); - - FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener); - - return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos); + public TrackExporter newTrackExporter(Context context, Track[] tracks, TrackExporterListener trackExporterListener) { + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); + TrackWriter trackWriter = this.newTrackWriter(context, tracks.length > 1); + return new FileTrackExporter(contentProviderUtils, trackWriter, tracks, trackExporterListener); } /** diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportAsyncTask.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportAsyncTask.java index 84c9b85d4..97f302eac 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportAsyncTask.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/ExportAsyncTask.java @@ -68,7 +68,7 @@ public class ExportAsyncTask extends AsyncTask { this.trackFileFormat = trackFileFormat; this.directory = directory; context = exportActivity.getApplicationContext(); - contentProviderUtils = ContentProviderUtils.Factory.get(context); + contentProviderUtils = new ContentProviderUtils(context); completed = false; processedTrackCount = 0; 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 891bd9559..c92e2655b 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 @@ -26,7 +26,8 @@ import androidx.annotation.NonNull; import java.io.OutputStream; import de.dennisguse.opentracks.content.ContentProviderUtils; -import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator; +import de.dennisguse.opentracks.content.LocationFactory; +import de.dennisguse.opentracks.content.LocationIterator; import de.dennisguse.opentracks.content.SensorDataSetLocation; import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.Waypoint; @@ -209,7 +210,7 @@ public class FileTrackExporter implements TrackExporter { * * @author Jimmy Shih */ - private class TrackWriterLocationFactory implements ContentProviderUtils.LocationFactory { + private class TrackWriterLocationFactory extends LocationFactory { Location currentLocation; Location lastLocation; 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 9d722b6f4..e93d95462 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 @@ -89,7 +89,7 @@ public class KmlTrackWriter implements TrackWriter { this.exportSensorData = exportSensorData; this.exportPhotos = exportPhotos; this.descriptionGenerator = new DescriptionGeneratorImpl(context); - this.contentProviderUtils = ContentProviderUtils.Factory.get(context); + this.contentProviderUtils = new ContentProviderUtils(context); } @Override 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 dc2e0f246..6555b368d 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 @@ -39,8 +39,9 @@ import javax.xml.parsers.SAXParserFactory; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.content.ContentProviderUtils; -import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator; import de.dennisguse.opentracks.content.DescriptionGeneratorImpl; +import de.dennisguse.opentracks.content.LocationFactory; +import de.dennisguse.opentracks.content.LocationIterator; import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.Waypoint; import de.dennisguse.opentracks.content.Waypoint.WaypointType; @@ -165,7 +166,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime()); TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(track.getTripStatistics().getStartTime()); - try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) { + try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) { while (true) { if (waypoint == null) { diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java index 2d4ee49a1..a71ef50ae 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java @@ -55,7 +55,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter { * @param context the context */ public GpxFileTrackImporter(Context context) { - this(context, ContentProviderUtils.Factory.get(context)); + this(context, new ContentProviderUtils(context)); } @VisibleForTesting diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java index 091e9399c..ffa33eb21 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/ImportAsyncTask.java @@ -169,7 +169,8 @@ public class ImportAsyncTask extends AsyncTask { if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) { trackImporter = new KmlFileTrackImporter(context, -1L); } else { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); + ; Uri uri = contentProviderUtils.insertTrack(new Track()); long newId = Long.parseLong(uri.getLastPathSegment()); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlFileTrackImporter.java index 2f4fa028e..6d12d0aed 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlFileTrackImporter.java @@ -81,7 +81,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter { * @param importTrackId track id to import to. -1L to import to a new track. */ public KmlFileTrackImporter(Context context, long importTrackId) { - this(context, importTrackId, ContentProviderUtils.Factory.get(context)); + this(context, importTrackId, new ContentProviderUtils(context)); } @VisibleForTesting diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java index 3f69c4e7f..2755f24ac 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmzTrackImporter.java @@ -102,7 +102,8 @@ public class KmzTrackImporter implements TrackImporter { */ private void cleanImport(long trackId) { if (PreferencesUtils.isRecording(trackId)) { - ContentProviderUtils contentProviderUtils = ContentProviderUtils.Factory.get(context); + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); + ; contentProviderUtils.deleteTrack(context, trackId); } diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 137536e97..b5c73d82b 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -43,9 +43,10 @@ import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.TrackDetailActivity; import de.dennisguse.opentracks.TrackListActivity; import de.dennisguse.opentracks.content.ContentProviderUtils; -import de.dennisguse.opentracks.content.ContentProviderUtils.LocationIterator; import de.dennisguse.opentracks.content.CustomContentProvider; import de.dennisguse.opentracks.content.DescriptionGeneratorImpl; +import de.dennisguse.opentracks.content.LocationFactory; +import de.dennisguse.opentracks.content.LocationIterator; import de.dennisguse.opentracks.content.SensorDataSetLocation; import de.dennisguse.opentracks.content.Track; import de.dennisguse.opentracks.content.Waypoint; @@ -214,7 +215,7 @@ public class TrackRecordingService extends Service { public void onCreate() { super.onCreate(); executorService = Executors.newSingleThreadExecutor(); - contentProviderUtils = ContentProviderUtils.Factory.get(this); + contentProviderUtils = new ContentProviderUtils(this); handler = new Handler(); locationManagerConnector = new LocationManagerConnector(this, handler.getLooper()); voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory()); @@ -506,7 +507,7 @@ public class TrackRecordingService extends Service { } markerTripStatisticsUpdater = new TripStatisticsUpdater(markerStartTime); - try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, ContentProviderUtils.DEFAULT_LOCATION_FACTORY)) { + try (LocationIterator locationIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false, LocationFactory.DEFAULT_LOCATION_FACTORY)) { while (locationIterator.hasNext()) { Location location = locationIterator.next(); diff --git a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java index 8fd460aad..4c9132266 100644 --- a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java @@ -76,7 +76,7 @@ public class IntentUtils { } String trackDescription = ""; if (trackIds.length == 1) { - Track track = ContentProviderUtils.Factory.get(context).getTrack(trackIds[0]); + Track track = new ContentProviderUtils(context).getTrack(trackIds[0]); trackDescription = track == null ? "" : new DescriptionGeneratorImpl(context).generateTrackDescription(track, false); }