Compare commits

..

43 Commits

Author SHA1 Message Date
Dennis Guse bfeca771f8 Release v3.9.4 2020-09-05 01:41:01 +02:00
Dennis Guse f128111650 Code cleanup. 2020-09-05 01:19:59 +02:00
Dennis Guse 38fadda762 Bugfix: show on map via GPX/KML/KMZ was failing.
We were sending the trackId as intent extra (actually unused) and this trackId could not be unparceled.
Fixes #411.
2020-09-05 01:19:59 +02:00
Dennis Guse d6d40b01e6 Refactoring.
Also fixes #406.
2020-09-05 01:17:40 +02:00
dependabot[bot] bc051f5d81 Bump mockito-android from 3.5.9 to 3.5.10
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.5.9 to 3.5.10.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.5.9...v3.5.10)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-04 23:45:30 +02:00
dependabot[bot] 39bf2190a1 Bump material from 1.2.0 to 1.2.1
Bumps [material](https://github.com/material-components/material-components-android) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.2.0...1.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-03 16:55:31 +02:00
Román b88f81726b Bugfix: location handler didn't remove updates. Fixes #403. 2020-09-03 15:32:44 +02:00
Dennis Guse 24e6cdec62 Interval Stats: fixed NPE introduced in 25ef0ecad1.
Includes some refactoring.
2020-09-02 23:12:52 +02:00
Dennis Guse 951671b2f3 Update PULL_REQUEST_TEMPLATE.md 2020-09-02 22:20:50 +02:00
dependabot[bot] 21e30545a0 Bump mockito-android from 3.4.6 to 3.5.9
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.4.6 to 3.5.9.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.4.6...v3.5.9)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-02 19:26:45 +02:00
Dennis Guse 946edb7e16 Update CONTRIBUTING.md 2020-09-02 19:24:33 +02:00
Román 89c44af374 Recorded stats: natural order for intervals. 2020-09-02 14:32:32 +02:00
Dennis Guse faad451e22 Added contribution guidelines 2020-09-02 08:10:55 +02:00
Dennis Guse 25ef0ecad1 Interval Stats: use a tab for recorded tracks as well. 2020-09-02 07:52:33 +02:00
Román efa6caca66 New style for spinner with text. 2020-09-02 07:52:33 +02:00
Román 5be4c107f4 Announcement: while recording report current interval pace or speed.
Fixes #134.
2020-09-02 07:52:33 +02:00
Román 65f42dd3e5 Interval Stats: improve code and fixed some UI issues. 2020-09-02 07:52:33 +02:00
Román 287d246777 Interval Stats: track's id it's not long anymore. 2020-09-02 07:52:33 +02:00
Román d56f652d0a Interval stats: while user is recording a track intervals are refreshed every 5 seconds.
Interval stats: completed the list of split intervals: 1, 2, 3, 4, 5, 10, 20 or 50 km or mi.
Fixes #87.
2020-09-02 07:52:33 +02:00
Román 1f1ef4445f Interval stats: added IntervalStatisticsTest. 2020-09-02 07:52:33 +02:00
Román 22a669a573 Interval stats: added a new tab in TrackRecordingActivity that shows the intervals on live. 2020-09-02 07:52:33 +02:00
Román f55cce5614 Interval stats: improve code. 2020-09-02 07:52:33 +02:00
Román a6c3cf1c28 Interval statistics: listen preferred units change (metric/imperial) and recreate intervals. 2020-09-02 07:52:33 +02:00
Román cbc803d038 Interval statistics: retain interval value after change orientation and recreation. 2020-09-02 07:52:33 +02:00
Román a8cefcd0fd Interval statistics: show accumulate in distance column. 2020-09-02 07:52:33 +02:00
Román 2b95dbab22 Adjusted intervals. 2020-09-02 07:52:33 +02:00
Román 7a923a947e Added intervals in StatisticsRecordedFragment with distance, speed and pace for all activity types.
Users can select the length of the interval (in a spinner with several options).
The intervals will be in km or mi depending on settings.
2020-09-02 07:52:33 +02:00
Román 19e35a7609 StatisticsRecordedFragment doesn't need TrackDataHub.
TrackRecordedActivity saves Track object instead of track's id.
2020-09-02 07:52:33 +02:00
Dennis Guse b8088169ee Settings: animate the Bluetooth icon.
Fixes #314.
2020-09-01 22:19:19 +02:00
Román a5594fbb7a Release v3.9.3 2020-08-31 12:47:33 +02:00
Román a733d87c37 Bugfix: export all failed trying to convert to string the Track.Id. 2020-08-31 12:25:58 +02:00
Dennis Guse 79d8ba9ef8 Configure Dependabot for Gradle and Github actions 2020-08-30 23:28:50 +02:00
Dennis Guse 3ee7d4504c Release v3.9.2 2020-08-30 23:05:51 +02:00
Dennis Guse 579509aad8 Upgrade dependencies. 2020-08-30 19:01:27 +02:00
Peter Storch 24ff73ab06 Fix force close on stop button for older devices (#393)
* correct thread handling in ButtonDelay

Discovered in #390 for devices with API Level 25. Not fixing the whole issue, but a first step.

* fix #390 crash when stopping for older devices

The VectorDrawable ic_button_stop.xml referenced from withing the AnimatedVectorDrawable ic_button_stop_anim.xml was a problem for older devices. Now the VectorDrawable is expanded (duplicated) inside the AnimatedVectorDrawable. Now it is working. Even with the animation.
2020-08-27 08:11:10 +02:00
Dennis Guse 60d5e7e515 Reset recordingTrackId on application Startup.
This is necessary if the TrackRecordingService crashed.
2020-08-23 17:36:30 +02:00
Dennis Guse c610758ae1 Move CursorLoader to ContentProviderUtils. 2020-08-23 00:09:02 +02:00
Dennis Guse 17d2755844 Bugfix: MarkerDetailActivity: compare waypointId properly using equals.
Introduced in bcbaf4985a
2020-08-23 00:08:58 +02:00
Dennis Guse 0ab14e0f3d Implement Track.Id and Waypoint.Id (with tests).
Fixes #276.
2020-08-22 23:18:48 +02:00
Dennis Guse bcbaf4985a Implement Track.Id and Waypoint.Id (without tests).
Fixes #276.
2020-08-22 23:18:48 +02:00
Dennis Guse b2e8713db0 Desugar: removed manual date parsing on API26. 2020-08-21 07:01:16 +02:00
Dennis Guse af60e1fdfb Enable Google's desugar plugin (enables stream/map etc.).
Fixes  #347.
2020-08-20 18:31:47 +02:00
Román 75043325ff Fix comment. 2020-08-19 22:04:40 +02:00
101 changed files with 2570 additions and 1213 deletions
+8
View File
@@ -1,5 +1,13 @@
# Thanks for your contribution.
## PLEASE REMOVE
To support us in providing a nice (and fast) open-source experience:
1. Verify that the tests are passing
2. Check that the code is properly formatted (using AndroidStudio's autoformatter)
3. Provide write access to the [branch](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)
4. If the PR is not ready for review, please submit it as a [draft](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests)
## PLEASE REMOVE
**Describe the pull request**
A clear and concise description of what the pull request changes/adds.
+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
+35
View File
@@ -0,0 +1,35 @@
# How to contribute?
## Testing
Just use OpenTracks and tell us what can be improved by opening an issue [here](https://github.com/OpenTracksApp/OpenTracks/issues/new/choose)
If we broke things (happens now and then), please tell us also.
## Coding new features
You have a cool idea for a feature that is missing in OpenTracks?
And you would like to help by implementing it?
If not, you can just take a look at the [issue tracker](https://github.com/OpenTracksApp/OpenTracks/issues).
Advices:
* creating an issue beforehand might be a good idea
Sometimes it is helpful to discuss your idea and also your implementation approach first.
We may be able to give you some hints on how to tackle the problem.
It also makes merging your changes easier and, usually, faster.
* please only add new dependencies if is absolutely necessary
If you need to:
* tell us why those are required,
* what alternatives are existing, and
* why you choose this dependency.
And also verify that the license(s) are compatible with _Apache 2.0_ as well as open-source.
## Coding conventions
None so far.
Just keep it simple.
And follow the _scout rule_.
You can take a look at existing code and get a feeling for it.
Please autoformat your code using AndroidStudio's code formatter.
+13 -9
View File
@@ -37,6 +37,8 @@ def getVersionName = { ->
android {
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@@ -45,8 +47,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 3530
versionName "v3.9.1"
versionCode 3573
versionName "v3.9.4"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -80,15 +82,17 @@ android {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.2.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'org.mockito:mockito-android:3.4.6'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'org.mockito:mockito-android:3.5.10'
}
@@ -0,0 +1,5 @@
v3.9.2: OpenTracks
Bugfixes:
- Fixed force close on API<25 (due to an animation)
- Reset recording state (on app startup) if crashed while recording
@@ -0,0 +1,4 @@
v3.9.3: OpenTracks
Bugfixes:
- Fixed export all tracks bug.
@@ -0,0 +1,10 @@
v3.9.4: OpenTracks
Changes:
- Added interval computation
- Voice announcements present last interval statistics
- Settings: Bluetooth icon is animated
Bugfix:
- GPS was not turned off when stopping a recording
- Show on map via KML/KMZ/GPX was broken due to refactoring
@@ -19,13 +19,16 @@ import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests {@link ChartValueSeries}.
*
@@ -60,16 +63,16 @@ public class ChartValueSeriesTest {
@Test
public void testInitialConditions() {
Assert.assertEquals(1, series.getInterval());
Assert.assertEquals(0, series.getMinMarkerValue());
Assert.assertEquals(5, series.getMaxMarkerValue());
Assert.assertTrue(series.isEnabled());
assertEquals(1, series.getInterval());
assertEquals(0, series.getMinMarkerValue());
assertEquals(5, series.getMaxMarkerValue());
assertTrue(series.isEnabled());
}
@Test
public void testEnabled() {
series.setEnabled(false);
Assert.assertFalse(series.isEnabled());
assertFalse(series.isEnabled());
}
@Test
@@ -77,9 +80,9 @@ public class ChartValueSeriesTest {
series.update(new ChartPoint(0));
series.update(new ChartPoint(10));
series.updateDimension();
Assert.assertEquals(100, series.getInterval());
Assert.assertEquals(0, series.getMinMarkerValue());
Assert.assertEquals(500, series.getMaxMarkerValue());
assertEquals(100, series.getInterval());
assertEquals(0, series.getMinMarkerValue());
assertEquals(500, series.getMaxMarkerValue());
}
@Test
@@ -87,9 +90,9 @@ public class ChartValueSeriesTest {
series.update(new ChartPoint(0));
series.update(new ChartPoint(901));
series.updateDimension();
Assert.assertEquals(1000, series.getInterval());
Assert.assertEquals(0, series.getMinMarkerValue());
Assert.assertEquals(5000, series.getMaxMarkerValue());
assertEquals(1000, series.getInterval());
assertEquals(0, series.getMinMarkerValue());
assertEquals(5000, series.getMaxMarkerValue());
}
@Test
@@ -97,8 +100,8 @@ public class ChartValueSeriesTest {
series.update(new ChartPoint(220));
series.update(new ChartPoint(250));
series.updateDimension();
Assert.assertEquals(100, series.getInterval());
Assert.assertEquals(200, series.getMinMarkerValue());
Assert.assertEquals(700, series.getMaxMarkerValue());
assertEquals(100, series.getInterval());
assertEquals(200, series.getMinMarkerValue());
assertEquals(700, series.getMaxMarkerValue());
}
}
@@ -21,7 +21,6 @@ import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +30,8 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link DescriptionGenerator}.
*
@@ -85,7 +86,7 @@ public class DescriptionGeneratorTest {
+ "Elevation gain: 6000 m (19685 ft)<br>"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
@Test
@@ -119,7 +120,7 @@ public class DescriptionGeneratorTest {
+ "Elevation gain: 6000 m (19685 ft)<br>"
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
Assert.assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
}
@@ -130,7 +131,7 @@ public class DescriptionGeneratorTest {
public void testWriteDistance() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeDistance(1100, builder, R.string.description_total_distance, "<br>");
Assert.assertEquals("Total distance: 1.10 km (0.7 mi)<br>", builder.toString());
assertEquals("Total distance: 1.10 km (0.7 mi)<br>", builder.toString());
}
/**
@@ -140,7 +141,7 @@ public class DescriptionGeneratorTest {
public void testWriteTime() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeTime(1000, builder, R.string.description_total_time, "<br>");
Assert.assertEquals("Total time: 00:01<br>", builder.toString());
assertEquals("Total time: 00:01<br>", builder.toString());
}
/**
@@ -150,7 +151,7 @@ public class DescriptionGeneratorTest {
public void testWriteSpeed() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeSpeed(1.1, builder, R.string.description_average_speed, "\n");
Assert.assertEquals("Average speed: 3.96 km/h (2.5 mi/h)\n", builder.toString());
assertEquals("Average speed: 3.96 km/h (2.5 mi/h)\n", builder.toString());
}
/**
@@ -160,7 +161,7 @@ public class DescriptionGeneratorTest {
public void testWriteElevation() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writeElevation(4.2, builder, R.string.description_min_elevation, "<br>");
Assert.assertEquals("Min elevation: 4 m (14 ft)<br>", builder.toString());
assertEquals("Min elevation: 4 m (14 ft)<br>", builder.toString());
}
/**
@@ -170,6 +171,6 @@ public class DescriptionGeneratorTest {
public void testWritePace() {
StringBuilder builder = new StringBuilder();
descriptionGenerator.writePace(1.1, builder, R.string.description_average_pace_in_minute, "\n");
Assert.assertEquals("Average pace: 15:09 min/km (24:23 min/mi)\n", builder.toString());
assertEquals("Average pace: 15:09 min/km (24:23 min/mi)\n", builder.toString());
}
}
@@ -24,7 +24,6 @@ import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.provider.ProviderTestRule;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -42,6 +41,10 @@ import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.provider.CustomSQLiteOpenHelper;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* Tests for {@link SearchEngine}.
* These are not meant to be quality tests, but instead feature-by-feature tests.
@@ -74,7 +77,7 @@ public class SearchEngineTest {
InstrumentationRegistry.getInstrumentation().getTargetContext().deleteDatabase(TAG + CustomSQLiteOpenHelper.DATABASE_NAME);
}
private long insertTrack(String title, String description, String category, long hoursAgo) {
private Track.Id insertTrack(String title, String description, String category, long hoursAgo) {
Track track = new Track();
track.setName(title);
track.setDescription(description);
@@ -88,18 +91,18 @@ public class SearchEngineTest {
}
Uri uri = providerUtils.insertTrack(track);
return ContentUris.parseId(uri);
return new Track.Id(ContentUris.parseId(uri));
}
private long insertTrack(String title, String description, String category) {
private Track.Id insertTrack(String title, String description, String category) {
return insertTrack(title, description, category, -1);
}
private long insertTrack(String title, long hoursAgo) {
private Track.Id insertTrack(String title, long hoursAgo) {
return insertTrack(title, "", "", hoursAgo);
}
private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) {
private Waypoint.Id insertWaypoint(String title, String description, String category, double distance, long hoursAgo, Track.Id trackId) {
Location location = new Location(HERE);
location.setLatitude(location.getLatitude() + distance);
location.setLongitude(location.getLongitude() + distance);
@@ -113,22 +116,22 @@ public class SearchEngineTest {
waypoint.setTrackId(trackId);
Uri uri = providerUtils.insertWaypoint(waypoint);
return ContentUris.parseId(uri);
return new Waypoint.Id(ContentUris.parseId(uri));
}
private long insertWaypoint(String title, String description, String category) {
return insertWaypoint(title, description, category, 0.0, -1, -1);
private Waypoint.Id insertWaypoint(String title, String description, String category) {
return insertWaypoint(title, description, category, 0.0, -1, null);
}
private long insertWaypoint(String title, double distance) {
return insertWaypoint(title, "", "", distance, -1, -1);
private Waypoint.Id insertWaypoint(String title, double distance) {
return insertWaypoint(title, "", "", distance, -1, null);
}
private long insertWaypoint(String title, long hoursAgo) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, -1);
private Waypoint.Id insertWaypoint(String title, long hoursAgo) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, null);
}
private long insertWaypoint(String title, long hoursAgo, long trackId) {
private Waypoint.Id insertWaypoint(String title, long hoursAgo, Track.Id trackId) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, trackId);
}
@@ -143,14 +146,14 @@ public class SearchEngineTest {
// - one which will match in title and description
// - one which will match in all fields
insertTrack("bb", "cc", "dd");
long descriptionMatchId = insertTrack("bb", "aa", "cc");
long categoryMatchId = insertTrack("bb", "cc", "aa");
long titleMatchId = insertTrack("aa", "bb", "cc");
long titleCategoryMatchId = insertTrack("aa", "bb", "ca");
long titleDescriptionMatchId = insertTrack("aa", "ba", "cc");
long allMatchId = insertTrack("aa", "ba", "ca");
Track.Id descriptionMatchId = insertTrack("bb", "aa", "cc");
Track.Id categoryMatchId = insertTrack("bb", "cc", "aa");
Track.Id titleMatchId = insertTrack("aa", "bb", "cc");
Track.Id titleCategoryMatchId = insertTrack("aa", "bb", "ca");
Track.Id titleDescriptionMatchId = insertTrack("aa", "ba", "cc");
Track.Id allMatchId = insertTrack("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
@@ -168,14 +171,14 @@ public class SearchEngineTest {
// - one which will match in title and description
// - one which will match in all fields
insertWaypoint("bb", "cc", "dd");
long descriptionMatchId = insertWaypoint("bb", "aa", "cc");
long categoryMatchId = insertWaypoint("bb", "cc", "aa");
long titleMatchId = insertWaypoint("aa", "bb", "cc");
long titleCategoryMatchId = insertWaypoint("aa", "bb", "ca");
long titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc");
long allMatchId = insertWaypoint("aa", "ba", "ca");
Waypoint.Id descriptionMatchId = insertWaypoint("bb", "aa", "cc");
Waypoint.Id categoryMatchId = insertWaypoint("bb", "cc", "aa");
Waypoint.Id titleMatchId = insertWaypoint("aa", "bb", "cc");
Waypoint.Id titleCategoryMatchId = insertWaypoint("aa", "bb", "ca");
Waypoint.Id titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc");
Waypoint.Id allMatchId = insertWaypoint("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
@@ -190,17 +193,17 @@ public class SearchEngineTest {
// - one waypoint which will match by title
// - one track which won't match
// - one track which will match by title
long descriptionWaypointId = insertWaypoint("bb", "aa", "cc");
Waypoint.Id descriptionWaypointId = insertWaypoint("bb", "aa", "cc");
insertWaypoint("bb", "cc", "dd");
long titleWaypointId = insertWaypoint("aa", "bb", "cc");
Waypoint.Id titleWaypointId = insertWaypoint("aa", "bb", "cc");
insertTrack("bb", "cc", "dd");
long trackId = insertTrack("aa", "bb", "cc");
Track.Id trackId = insertTrack("aa", "bb", "cc");
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category.
Assert.assertEquals(results.toString(), 3, results.size());
assertEquals(results.toString(), 3, results.size());
assertTrackResult(trackId, results.get(0));
assertWaypointResult(titleWaypointId, results.get(1));
assertWaypointResult(descriptionWaypointId, results.get(2));
@@ -209,11 +212,11 @@ public class SearchEngineTest {
@Test
public void testSearchWaypointDistance() {
// All results match text, but they're at difference distances from the user.
long farFarAwayId = insertWaypoint("aa", 0.3);
long nearId = insertWaypoint("ab", 0.1);
long farId = insertWaypoint("ac", 0.2);
Waypoint.Id farFarAwayId = insertWaypoint("aa", 0.3);
Waypoint.Id nearId = insertWaypoint("ab", 0.1);
Waypoint.Id farId = insertWaypoint("ac", 0.2);
SearchQuery query = new SearchQuery("a", HERE, -1, NOW);
SearchQuery query = new SearchQuery("a", HERE, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Distance order.
@@ -222,11 +225,11 @@ public class SearchEngineTest {
public void testSearchTrackRecent() {
// All results match text, but they're were recorded at different times.
long oldestId = insertTrack("aa", 3);
long recentId = insertTrack("ab", 1);
long oldId = insertTrack("ac", 2);
Track.Id oldestId = insertTrack("aa", 3);
Track.Id recentId = insertTrack("ab", 1);
Track.Id oldId = insertTrack("ac", 2);
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order.
@@ -236,11 +239,11 @@ public class SearchEngineTest {
@Test
public void testSearchWaypointRecent() {
// All results match text, but they're were recorded at different times.
long oldestId = insertWaypoint("aa", 2);
long recentId = insertWaypoint("ab", 0);
long oldId = insertWaypoint("ac", 1);
Waypoint.Id oldestId = insertWaypoint("aa", 2);
Waypoint.Id recentId = insertWaypoint("ab", 0);
Waypoint.Id oldId = insertWaypoint("ac", 1);
SearchQuery query = new SearchQuery("a", null, -1, NOW);
SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order.
@@ -250,8 +253,8 @@ public class SearchEngineTest {
@Test
public void testSearchCurrentTrack() {
// All results match text, but one of them is the current track.
long currentId = insertTrack("ab", 1);
long otherId = insertTrack("aa", 1);
Track.Id currentId = insertTrack("ab", 1);
Track.Id otherId = insertTrack("aa", 1);
SearchQuery query = new SearchQuery("a", null, currentId, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
@@ -263,47 +266,57 @@ public class SearchEngineTest {
@Test
public void testSearchCurrentTrackWaypoint() {
// All results match text, but one of them is in the current track.
long otherId = insertWaypoint("aa", 1, 456);
long currentId = insertWaypoint("ab", 1, 123);
Waypoint.Id otherId = insertWaypoint("aa", 1, new Track.Id(456));
Waypoint.Id currentId = insertWaypoint("ab", 1, new Track.Id(123));
SearchQuery query = new SearchQuery("a", null, 123, NOW);
SearchQuery query = new SearchQuery("a", null, new Track.Id(123), NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Waypoint in current track should be promoted.
assertWaypointResults(results, currentId, otherId);
}
private void assertTrackResult(long trackId, ScoredResult result) {
Assert.assertNotNull("Not a track", result.track);
Assert.assertNull("Ambiguous result", result.waypoint);
Assert.assertEquals(trackId, result.track.getId());
private void assertTrackResult(Track.Id trackId, ScoredResult result) {
assertNotNull("Not a track", result.track);
assertNull("Ambiguous result", result.waypoint);
assertEquals(trackId, result.track.getId());
}
private void assertTrackResults(List<ScoredResult> results, long... trackIds) {
String errMsg = "Expected IDs=" + Arrays.toString(trackIds) + "; results=" + results;
Assert.assertEquals(results.size(), trackIds.length);
private void assertTrackResults(List<ScoredResult> results, Track.Id... trackIds) {
long[] longTrackIds = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
longTrackIds[i] = trackIds[i].getId();
}
String errMsg = "Expected IDs=" + Arrays.toString(longTrackIds) + "; results=" + results;
assertEquals(results.size(), trackIds.length);
for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i);
Assert.assertNotNull(errMsg, result.track);
Assert.assertNull(errMsg, result.waypoint);
Assert.assertEquals(errMsg, trackIds[i], result.track.getId());
assertNotNull(errMsg, result.track);
assertNull(errMsg, result.waypoint);
assertEquals(errMsg, trackIds[i], result.track.getId());
}
}
private void assertWaypointResult(long waypointId, ScoredResult result) {
Assert.assertNotNull("Not a waypoint", result.waypoint);
Assert.assertNull("Ambiguous result", result.track);
Assert.assertEquals(waypointId, result.waypoint.getId());
private void assertWaypointResult(Waypoint.Id waypointId, ScoredResult result) {
assertNotNull("Not a waypoint", result.waypoint);
assertNull("Ambiguous result", result.track);
assertEquals(waypointId, result.waypoint.getId());
}
private void assertWaypointResults(List<ScoredResult> results, long... waypointIds) {
String errMsg = "Expected IDs=" + Arrays.toString(waypointIds) + "; results=" + results;
Assert.assertEquals(results.size(), waypointIds.length);
private void assertWaypointResults(List<ScoredResult> results, Waypoint.Id... waypointIds) {
long[] longWaypointIds = new long[waypointIds.length];
for (int i = 0; i < waypointIds.length; i++) {
longWaypointIds[i] = waypointIds[i].getId();
}
String errMsg = "Expected IDs=" + Arrays.toString(longWaypointIds) + "; results=" + results;
assertEquals(results.size(), waypointIds.length);
for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i);
Assert.assertNotNull(errMsg, result.waypoint);
Assert.assertNull(errMsg, result.track);
Assert.assertEquals(errMsg, waypointIds[i], result.waypoint.getId());
assertNotNull(errMsg, result.waypoint);
assertNull(errMsg, result.track);
assertEquals(errMsg, waypointIds[i], result.waypoint.getId());
}
}
}
@@ -20,10 +20,10 @@ public class TestDataUtil {
/**
* Create a track without any trackPoints.
*/
public static Track createTrack(long trackId) {
public static Track createTrack(Track.Id trackId) {
Track track = new Track();
track.setId(trackId);
track.setName("Test: " + trackId);
track.setName("Test: " + trackId.getId());
return track;
}
@@ -34,7 +34,7 @@ public class TestDataUtil {
* @param trackId the trackId of the track
* @param numPoints the trackPoints number in the track
*/
public static Pair<Track, TrackPoint[]> createTrack(long trackId, int numPoints) {
public static Pair<Track, TrackPoint[]> createTrack(Track.Id trackId, int numPoints) {
Track track = createTrack(trackId);
TrackPoint[] trackPoints = new TrackPoint[numPoints];
@@ -45,7 +45,7 @@ public class TestDataUtil {
return new Pair<>(track, trackPoints);
}
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, long trackId, int numPoints) {
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, Track.Id trackId, int numPoints) {
Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints);
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
@@ -93,7 +93,7 @@ public class TestDataUtil {
* @param location The location.
* @return the Waypoint created.
*/
public static Waypoint createWaypointWithPhoto(Context context, long trackId, Location location) throws IOException {
public static Waypoint createWaypointWithPhoto(Context context, Track.Id trackId, Location location) throws IOException {
File dstFile = new File(FileUtils.getImageUrl(context, trackId));
dstFile.createNewFile();
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
@@ -25,7 +25,6 @@ import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,6 +49,11 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.UUIDUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
/**
@@ -83,62 +87,62 @@ public class CustomContentProviderUtilsTest {
@Test
public void testLocationIterator_noPoints() {
testIterator(1, 0, 1, false);
testIterator(new Track.Id(1), 0, 1, false);
}
@Test
public void testLocationIterator_noBatchAscending() {
testIterator(1, 50, 100, false);
testIterator(2, 50, 50, false);
testIterator(new Track.Id(1), 50, 100, false);
testIterator(new Track.Id(2), 50, 50, false);
}
@Test
public void testLocationIterator_noBatchDescending() {
testIterator(1, 50, 100, true);
testIterator(2, 50, 50, true);
testIterator(new Track.Id(1), 50, 100, true);
testIterator(new Track.Id(2), 50, 50, true);
}
@Test
public void testLocationIterator_batchAscending() {
testIterator(1, 50, 11, false);
testIterator(2, 50, 25, false);
testIterator(new Track.Id(1), 50, 11, false);
testIterator(new Track.Id(2), 50, 25, false);
}
@Test
public void testLocationIterator_batchDescending() {
testIterator(1, 50, 11, true);
testIterator(2, 50, 25, true);
testIterator(new Track.Id(1), 50, 11, true);
testIterator(new Track.Id(2), 50, 25, true);
}
@Test
public void testLocationIterator_largeTrack() {
testIterator(1, 20000, 2000, false);
testIterator(new Track.Id(1), 20000, 2000, false);
}
private void testIterator(long trackId, int numPoints, int batchSize, boolean descending) {
private void testIterator(Track.Id trackId, int numPoints, int batchSize, boolean descending) {
long lastPointId = initializeTrack(trackId, numPoints);
contentProviderUtils.setDefaultCursorBatchSize(batchSize);
List<TrackPoint> locations = new ArrayList<>(numPoints);
try (TrackPointIterator it = contentProviderUtils.getTrackPointLocationIterator(trackId, -1L, descending)) {
while (it.hasNext()) {
TrackPoint loc = it.next();
Assert.assertNotNull(loc);
assertNotNull(loc);
locations.add(loc);
// Make sure the IDs are returned in the right order.
Assert.assertEquals(descending ? lastPointId - locations.size() + 1
assertEquals(descending ? lastPointId - locations.size() + 1
: lastPointId - numPoints + locations.size(), it.getTrackPointId());
}
Assert.assertEquals(numPoints, locations.size());
assertEquals(numPoints, locations.size());
}
}
private long initializeTrack(long id, int numPoints) {
private long initializeTrack(Track.Id id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setName("Test: " + id.getId());
contentProviderUtils.insertTrack(track);
track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track);
assertNotNull(track);
TrackPoint[] trackPoints = new TrackPoint[numPoints];
for (int i = 0; i < numPoints; ++i) {
@@ -162,8 +166,8 @@ public class CustomContentProviderUtilsTest {
}
}
Assert.assertTrue(numPoints == 0 || lastPointId > 0);
Assert.assertEquals(numPoints, counter);
assertTrue(numPoints == 0 || lastPointId > 0);
assertEquals(numPoints, counter);
return lastPointId;
}
@@ -173,13 +177,13 @@ public class CustomContentProviderUtilsTest {
*/
@Test
public void testCreateTrack() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
int columnIndex = 1;
// Id
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex);
when(cursorMock.isNull(columnIndex)).thenReturn(false);
when(cursorMock.getLong(columnIndex)).thenReturn(trackId);
when(cursorMock.getLong(columnIndex)).thenReturn(trackId.getId());
//Uuid
columnIndex++;
@@ -189,14 +193,14 @@ public class CustomContentProviderUtilsTest {
// Name
columnIndex++;
String name = NAME_PREFIX + trackId;
String name = NAME_PREFIX + trackId.getId();
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex);
when(cursorMock.isNull(columnIndex)).thenReturn(false);
when(cursorMock.getString(columnIndex)).thenReturn(name);
Track track = contentProviderUtils.createTrack(cursorMock);
Assert.assertEquals(trackId, track.getId());
Assert.assertEquals(name, track.getName());
assertEquals(trackId, track.getId());
assertEquals(name, track.getName());
}
/**
@@ -205,7 +209,7 @@ public class CustomContentProviderUtilsTest {
@Test
public void testDeleteAllTracks() {
// Insert track, points and waypoint at first.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
@@ -213,20 +217,20 @@ public class CustomContentProviderUtilsTest {
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(1, tracksCursor.getCount());
assertEquals(1, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(10, tracksPointsCursor.getCount());
assertEquals(10, tracksPointsCursor.getCount());
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount());
assertEquals(1, waypointCursor.getCount());
// Delete all.
contentProviderUtils.deleteAllTracks(context);
// Check whether all have been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(0, tracksCursor.getCount());
assertEquals(0, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(0, tracksPointsCursor.getCount());
assertEquals(0, tracksPointsCursor.getCount());
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(0, waypointCursor.getCount());
assertEquals(0, waypointCursor.getCount());
}
/**
@@ -235,7 +239,7 @@ public class CustomContentProviderUtilsTest {
@Test
public void testDeleteAllTracks_withWaypointAndPhoto() throws IOException {
// Insert track, points and waypoint with photo at first.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
@@ -244,130 +248,137 @@ public class CustomContentProviderUtilsTest {
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(1, tracksCursor.getCount());
assertEquals(1, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(10, tracksPointsCursor.getCount());
assertEquals(10, tracksPointsCursor.getCount());
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount());
assertEquals(1, waypointCursor.getCount());
// Check waypoint has photo and it's in the external storage.
Assert.assertTrue(waypoint.hasPhoto());
assertTrue(waypoint.hasPhoto());
File dir = FileUtils.getPhotoDir(context, trackId);
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length);
Assert.assertTrue(dir.exists());
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
assertTrue(dir.exists());
// Delete all.
contentProviderUtils.deleteAllTracks(context);
// Check whether all have been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(0, tracksCursor.getCount());
assertEquals(0, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(0, tracksPointsCursor.getCount());
assertEquals(0, tracksPointsCursor.getCount());
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(0, waypointCursor.getCount());
Assert.assertFalse(dir.exists());
assertEquals(0, waypointCursor.getCount());
assertFalse(dir.exists());
}
/**
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, Track.Id)}.
*/
@Test
public void testDeleteTrack() {
// Insert three tracks, points of two tracks and way point of one track.
long random = System.currentTimeMillis();
Track.Id trackId1 = new Track.Id(random);
Track.Id trackId2 = new Track.Id(random + 1);
Track.Id trackId3 = new Track.Id(random + 2);
long trackId = System.currentTimeMillis();
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 0);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId1, 0);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId + 1));
waypoint.setTrackId(trackId);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId2));
waypoint.setTrackId(trackId1);
contentProviderUtils.insertWaypoint(waypoint);
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(3, tracksCursor.getCount());
assertEquals(3, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(20, tracksPointsCursor.getCount());
assertEquals(20, tracksPointsCursor.getCount());
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount());
assertEquals(1, waypointCursor.getCount());
// Delete one track.
contentProviderUtils.deleteTrack(context, trackId);
contentProviderUtils.deleteTrack(context, trackId1);
// Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(2, tracksCursor.getCount());
assertEquals(2, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(20, tracksPointsCursor.getCount());
assertEquals(20, tracksPointsCursor.getCount());
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(0, waypointCursor.getCount());
assertEquals(0, waypointCursor.getCount());
}
/**
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}.
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, Track.Id)}.
*/
@Test
public void testDeleteTrack_withWaypointPhoto() throws IOException {
// Insert three tracks.
long trackId = System.currentTimeMillis();
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10);
long random = System.currentTimeMillis();
Track.Id trackId1 = new Track.Id(random);
Track.Id trackId2 = new Track.Id(random + 1);
Track.Id trackId3 = new Track.Id(random + 2);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId1, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
// Insert a waypoint in tracks trackId and trackId + 1.
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId);
Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint1.getLocation());
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId1);
Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId1, trackPoint1.getLocation());
contentProviderUtils.insertWaypoint(waypoint1);
File dir1 = FileUtils.getPhotoDir(context, trackId);
File dir1 = FileUtils.getPhotoDir(context, trackId1);
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId + 1);
Waypoint waypoint2 = TestDataUtil.createWaypointWithPhoto(context, trackId + 1, trackPoint2.getLocation());
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId2);
Waypoint waypoint2 = TestDataUtil.createWaypointWithPhoto(context, trackId2, trackPoint2.getLocation());
contentProviderUtils.insertWaypoint(waypoint2);
File dir2 = FileUtils.getPhotoDir(context, trackId + 1);
File dir2 = FileUtils.getPhotoDir(context, trackId2);
// Check.
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(3, tracksCursor.getCount());
assertEquals(3, tracksCursor.getCount());
Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(30, tracksPointsCursor.getCount());
assertEquals(30, tracksPointsCursor.getCount());
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(2, waypointCursor.getCount());
Assert.assertTrue(waypoint1.hasPhoto());
Assert.assertTrue(dir1.isDirectory());
Assert.assertEquals(1, dir1.list().length);
Assert.assertTrue(dir1.exists());
Assert.assertTrue(dir2.isDirectory());
Assert.assertEquals(1, dir2.list().length);
Assert.assertTrue(dir2.exists());
assertEquals(2, waypointCursor.getCount());
assertTrue(waypoint1.hasPhoto());
assertTrue(dir1.isDirectory());
assertEquals(1, dir1.list().length);
assertTrue(dir1.exists());
assertTrue(dir2.isDirectory());
assertEquals(1, dir2.list().length);
assertTrue(dir2.exists());
// Delete one track.
contentProviderUtils.deleteTrack(context, trackId);
contentProviderUtils.deleteTrack(context, trackId1);
// Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(2, tracksCursor.getCount());
assertEquals(2, tracksCursor.getCount());
tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID);
Assert.assertEquals(20, tracksPointsCursor.getCount());
assertEquals(20, tracksPointsCursor.getCount());
waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount());
Assert.assertFalse(dir1.exists());
Assert.assertTrue(dir2.exists());
assertEquals(1, waypointCursor.getCount());
assertFalse(dir1.exists());
assertTrue(dir2.exists());
}
/**
* Tests the method {@link ContentProviderUtils#getAllTracks()}
* Tests the method {@link ContentProviderUtils#getTracks()}
*/
@Test
public void testGetAllTracks() {
// given
int initialTrackNumber = contentProviderUtils.getAllTracks().size();
long trackId = System.currentTimeMillis();
int initialTrackNumber = contentProviderUtils.getTracks().size();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
// when
List<Track> allTracks = contentProviderUtils.getAllTracks();
List<Track> allTracks = contentProviderUtils.getTracks();
// then
Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
Assert.assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
assertEquals(initialTrackNumber + 1, allTracks.size());
assertEquals(trackId, allTracks.get(allTracks.size() - 1).getId());
}
/**
@@ -375,36 +386,36 @@ public class CustomContentProviderUtilsTest {
*/
@Test
public void testGetLastTrack() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
}
/**
* Tests the method {@link ContentProviderUtils#getTrack(long)}
* Tests the method {@link ContentProviderUtils#getTrack(Track.Id)}
*/
@Test
public void testGetTrack_by_id() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
// when / then
Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
assertNotNull(contentProviderUtils.getTrack(trackId));
}
/**
* Tests the method {@link ContentProviderUtils#getTrack(long)}
* Tests the method {@link ContentProviderUtils#getTrack(Track.Id)}
*/
@Test
public void testGetTrack_by_uuid() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrack(trackId);
contentProviderUtils.insertTrack(track);
// when / then
Assert.assertNotNull(contentProviderUtils.getTrack(track.getUuid()));
assertNotNull(contentProviderUtils.getTrack(track.getUuid()));
}
/**
@@ -413,7 +424,7 @@ public class CustomContentProviderUtilsTest {
@Test
public void testUpdateTrack() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrack(trackId);
String nameOld = "name1";
String nameNew = "name2";
@@ -421,10 +432,10 @@ public class CustomContentProviderUtilsTest {
// when / then
contentProviderUtils.insertTrack(track);
Assert.assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
assertEquals(nameOld, contentProviderUtils.getTrack(trackId).getName());
track.setName(nameNew);
contentProviderUtils.updateTrack(track);
Assert.assertEquals(nameNew, contentProviderUtils.getTrack(trackId).getName());
assertEquals(nameNew, contentProviderUtils.getTrack(trackId).getName());
}
/**
@@ -432,7 +443,7 @@ public class CustomContentProviderUtilsTest {
*/
@Test
public void testCreateContentValues_waypoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
// Bottom
@@ -458,12 +469,12 @@ public class CustomContentProviderUtilsTest {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
long waypointId = System.currentTimeMillis();
Waypoint.Id waypointId = new Waypoint.Id(System.currentTimeMillis());
waypoint.setId(waypointId);
ContentValues contentValues = contentProviderUtils.createContentValues(waypoint);
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID));
Assert.assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(WaypointsColumns.LONGITUDE));
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
assertEquals(waypointId.getId(), contentValues.get(WaypointsColumns._ID));
assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(WaypointsColumns.LONGITUDE));
assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
}
/**
@@ -495,19 +506,19 @@ public class CustomContentProviderUtilsTest {
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
Waypoint waypoint = contentProviderUtils.createWaypoint(cursorMock);
Assert.assertEquals(id, waypoint.getId());
Assert.assertEquals(name, waypoint.getName());
Assert.assertEquals(trackId, waypoint.getTrackId());
assertEquals(id, waypoint.getId().getId());
assertEquals(name, waypoint.getName());
assertEquals(trackId, waypoint.getTrackId().getId());
}
/**
* Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* {@link ContentProviderUtils#deleteWaypoint(Context, Waypoint.Id)}
* when there is only one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_onlyOneWayPoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
@@ -517,25 +528,25 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.insertWaypoint(waypoint1);
// Check insert was done.
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
// Get waypoint id that needs to delete.
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
Waypoint.Id waypoint1Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)));
// Delete
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
}
/**
* Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long)}
* {@link ContentProviderUtils#deleteWaypoint(Context, Waypoint.Id)}
* when there is only one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_onlyOneWayPointWithPhotoUrl() throws IOException {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
@@ -544,32 +555,32 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.insertWaypoint(waypoint1);
// Check insert was done.
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
// Get waypoint id that needs to delete.
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
Waypoint.Id waypoint1Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)));
// Check waypoint has photo and it's in the external storage.
Assert.assertTrue(waypoint1.hasPhoto());
assertTrue(waypoint1.hasPhoto());
File dir = FileUtils.getPhotoDir(context, trackId);
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length);
Assert.assertTrue(dir.exists());
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
assertTrue(dir.exists());
// Delete
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
// Check waypoint doesn't exists and photo folder was deleted.
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
Assert.assertFalse(dir.exists());
assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
assertFalse(dir.exists());
}
/**
* Tests the method {@link ContentProviderUtils#deleteWaypoint(Context, long)} when there is more than one waypoint in the track.
* Tests the method {@link ContentProviderUtils#deleteWaypoint(Context, Waypoint.Id)} when there is more than one waypoint in the track.
*/
@Test
public void testDeleteWaypoint_hasNextWayPoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Track track = TestDataUtil.createTrackAndInsert(trackId, 10);
@@ -592,27 +603,27 @@ public class CustomContentProviderUtilsTest {
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint1.setDescription(MOCK_DESC);
waypoint1.setTrackId(trackId);
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1));
Waypoint.Id waypoint1Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)));
Waypoint waypoint2 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint2.setDescription(MOCK_DESC);
waypoint2.setTrackId(trackId);
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2));
Waypoint.Id waypoint2Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2)));
// Delete
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
contentProviderUtils.deleteWaypoint(context, waypoint1Id);
Assert.assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
assertNull(contentProviderUtils.getWaypoint(waypoint1Id));
Assert.assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
assertEquals(MOCK_DESC, contentProviderUtils.getWaypoint(waypoint2Id).getDescription());
}
/**
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long)}.
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(Track.Id)}.
*/
@Test
public void testGetNextWaypointNumber() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
@@ -628,24 +639,24 @@ public class CustomContentProviderUtilsTest {
contentProviderUtils.insertWaypoint(waypoint3);
contentProviderUtils.insertWaypoint(waypoint4);
Assert.assertEquals(4, contentProviderUtils.getNextWaypointNumber(trackId));
assertEquals(4, contentProviderUtils.getNextWaypointNumber(trackId));
}
/**
* Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and
* {@link ContentProviderUtils#getWaypoint(long)}.
* {@link ContentProviderUtils#getWaypoint(Waypoint.Id)}.
*/
@Test
public void testInsertAndGetWaypoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
}
/**
@@ -653,21 +664,21 @@ public class CustomContentProviderUtilsTest {
*/
@Test
public void testUpdateWaypoint() {
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
// Update
waypoint = contentProviderUtils.getWaypoint(waypointId);
waypoint.setDescription(TEST_DESC_NEW);
contentProviderUtils.updateWaypoint(context, waypoint);
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
}
/**
@@ -677,7 +688,7 @@ public class CustomContentProviderUtilsTest {
public void testUpdateWaypoint_withPhoto() throws IOException {
// tests after update waypoint with photo the photo remains in the storage.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
@@ -685,12 +696,12 @@ public class CustomContentProviderUtilsTest {
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
assertTrue(dir.exists());
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
// Update
waypoint = contentProviderUtils.getWaypoint(waypointId);
@@ -698,12 +709,12 @@ public class CustomContentProviderUtilsTest {
waypoint.setDescription(TEST_DESC_NEW);
contentProviderUtils.updateWaypoint(context, waypoint);
Assert.assertEquals(TEST_NAME_NEW, contentProviderUtils.getWaypoint(waypointId).getName());
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
Assert.assertTrue(waypoint.hasPhoto());
Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length);
assertEquals(TEST_NAME_NEW, contentProviderUtils.getWaypoint(waypointId).getName());
assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
assertTrue(waypoint.hasPhoto());
assertTrue(dir.exists());
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
}
/**
@@ -713,7 +724,7 @@ public class CustomContentProviderUtilsTest {
public void testUpdateWaypoint_delPhotoAndDir() throws IOException {
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Also empty directory is deleted.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first.
@@ -721,12 +732,12 @@ public class CustomContentProviderUtilsTest {
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
assertTrue(dir.exists());
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
// Update
waypoint = contentProviderUtils.getWaypoint(waypointId);
@@ -735,10 +746,10 @@ public class CustomContentProviderUtilsTest {
waypoint.setPhotoUrl(null);
contentProviderUtils.updateWaypoint(context, waypoint);
Assert.assertEquals(TEST_NAME_NEW, contentProviderUtils.getWaypoint(waypointId).getName());
Assert.assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
Assert.assertFalse(waypoint.hasPhoto());
Assert.assertFalse(dir.exists());
assertEquals(TEST_NAME_NEW, contentProviderUtils.getWaypoint(waypointId).getName());
assertEquals(TEST_DESC_NEW, contentProviderUtils.getWaypoint(waypointId).getDescription());
assertFalse(waypoint.hasPhoto());
assertFalse(dir.exists());
}
/**
@@ -748,7 +759,7 @@ public class CustomContentProviderUtilsTest {
public void testUpdateWaypoint_delPhotoNotDir() throws IOException {
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Directory remains if there are more photos from other waypoints.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert two waypoints with photos.
@@ -759,41 +770,41 @@ public class CustomContentProviderUtilsTest {
Waypoint otherWaypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
otherWaypoint.setDescription(TEST_DESC);
otherWaypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint));
long otherWaypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(otherWaypoint));
Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
contentProviderUtils.insertWaypoint(otherWaypoint);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId);
Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(2, dir.list().length);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
assertTrue(dir.exists());
assertTrue(dir.isDirectory());
assertEquals(2, dir.list().length);
// Update one waypoint deleting photo.
waypoint = contentProviderUtils.getWaypoint(waypointId);
waypoint.setPhotoUrl(null);
contentProviderUtils.updateWaypoint(context, waypoint);
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
Assert.assertFalse(waypoint.hasPhoto());
Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length);
assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
assertFalse(waypoint.hasPhoto());
assertTrue(dir.exists());
assertTrue(dir.isDirectory());
assertEquals(1, dir.list().length);
}
/**
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], long)}.
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], Track.Id)}.
*/
@Test
public void testBulkInsertTrackPoint() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second);
// when / then
contentProviderUtils.bulkInsertTrackPoint(track.second, trackId);
Assert.assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
assertEquals(20, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
contentProviderUtils.bulkInsertTrackPoint(Arrays.copyOfRange(track.second, 0, 8), trackId);
Assert.assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
assertEquals(28, contentProviderUtils.getTrackPointCursor(trackId, -1L, 1000, false).getCount());
}
/**
@@ -832,31 +843,31 @@ public class CustomContentProviderUtilsTest {
TrackPoint trackPoint = contentProviderUtils.createTrackPoint(cursorMock);
// then
Assert.assertEquals(longitude, trackPoint.getLongitude(), 0.01);
Assert.assertEquals(latitude, trackPoint.getLatitude(), 0.01);
Assert.assertEquals(time, trackPoint.getTime(), 0.01);
Assert.assertEquals(speed, trackPoint.getSpeed(), 0.01);
Assert.assertFalse(trackPoint.hasHeartRate());
assertEquals(longitude, trackPoint.getLongitude(), 0.01);
assertEquals(latitude, trackPoint.getLatitude(), 0.01);
assertEquals(time, trackPoint.getTime(), 0.01);
assertEquals(speed, trackPoint.getSpeed(), 0.01);
assertFalse(trackPoint.hasHeartRate());
}
/**
* Tests the method
* {@link ContentProviderUtils#insertTrackPoint(TrackPoint, long)}.
* {@link ContentProviderUtils#insertTrackPoint(TrackPoint, Track.Id)}.
*/
@Test
public void testInsertTrackPoint() {
// Insert track, point at first.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
Assert.assertEquals(11, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(11, contentProviderUtils.getTrackPoints(trackId).size());
}
@Test
public void testInsertAndLoadTrackPoint() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
@@ -869,19 +880,19 @@ public class CustomContentProviderUtilsTest {
// then
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
Assert.assertTrue(trackPoints.get(10).hasHeartRate());
Assert.assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
Assert.assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
Assert.assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower(), 0.01);
assertTrue(trackPoints.get(10).hasHeartRate());
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower(), 0.01);
}
/**
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(long)}.
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(Track.Id)}.
*/
@Test
public void testGetLastValidTrackPoint() {
// Insert track, points at first.
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
@@ -889,12 +900,12 @@ public class CustomContentProviderUtilsTest {
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in descending.
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in descending.
*/
@Test
public void testGetTrackPointCursor_desc() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
@@ -907,16 +918,16 @@ public class CustomContentProviderUtilsTest {
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[1], 5, true);
// then
Assert.assertEquals(2, cursor.getCount());
assertEquals(2, cursor.getCount());
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in ascending.
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in ascending.
*/
@Test
public void testGetTrackPointCursor_asc() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
@@ -929,16 +940,16 @@ public class CustomContentProviderUtilsTest {
Cursor cursor = contentProviderUtils.getTrackPointCursor(trackId, trackpointIds[8], 5, false);
// then
Assert.assertEquals(2, cursor.getCount());
assertEquals(2, cursor.getCount());
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in descending.
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in descending.
*/
@Test
public void testGetTrackPointLocationIterator_desc() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
@@ -953,21 +964,21 @@ public class CustomContentProviderUtilsTest {
// then
for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(trackPointIterator.hasNext());
assertTrue(trackPointIterator.hasNext());
TrackPoint trackPoint = trackPointIterator.next();
Assert.assertEquals(startTrackPointId - i, trackPointIterator.getTrackPointId());
assertEquals(startTrackPointId - i, trackPointIterator.getTrackPointId());
checkLocation((trackpointIds.length - 1) - i, trackPoint.getLocation());
}
Assert.assertFalse(trackPointIterator.hasNext());
assertFalse(trackPointIterator.hasNext());
}
/**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in ascending.
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in ascending.
*/
@Test
public void testGetTrackPointLocationIterator_asc() {
// given
long trackId = System.currentTimeMillis();
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first);
@@ -983,34 +994,32 @@ public class CustomContentProviderUtilsTest {
// then
for (int i = 0; i < trackpointIds.length; i++) {
Assert.assertTrue(trackPointIterator.hasNext());
assertTrue(trackPointIterator.hasNext());
TrackPoint trackPoint = trackPointIterator.next();
Assert.assertEquals(startTrackPointId + i, trackPointIterator.getTrackPointId());
assertEquals(startTrackPointId + i, trackPointIterator.getTrackPointId());
checkLocation(i, trackPoint.getLocation());
}
Assert.assertFalse(trackPointIterator.hasNext());
assertFalse(trackPointIterator.hasNext());
}
/**
* Checks the value of a location.
*
* @param i the index of this location which created in the method {@link TestDataUtil#createTrack(long, int)}
* @param i the index of this location which created in the method {@link TestDataUtil#createTrack(Track.Id, int)}
* @param location the location to be checked
*/
private void checkLocation(int i, Location location) {
Assert.assertEquals(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
Assert.assertEquals(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
Assert.assertEquals((float) i / 100.0f, location.getAccuracy(), 0.01);
Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
assertEquals(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0, location.getLatitude(), 0.01);
assertEquals(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0, location.getLongitude(), 0.01);
assertEquals((float) i / 100.0f, location.getAccuracy(), 0.01);
assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
}
@Test
public void testFormatIdListForUri() {
Assert.assertEquals("", ContentProviderUtils.formatIdListForUri(new long[]{}));
Assert.assertEquals("12", ContentProviderUtils.formatIdListForUri(new long[]{12}));
Assert.assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new long[]{42, 43, 44}));
assertEquals("", ContentProviderUtils.formatIdListForUri());
assertEquals("12", ContentProviderUtils.formatIdListForUri(new Track.Id(12)));
assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new Track.Id(42), new Track.Id(43), new Track.Id(44)));
}
}
@@ -3,33 +3,43 @@ package de.dennisguse.opentracks.content.provider;
import android.net.Uri;
import android.util.Pair;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import static org.junit.Assert.assertEquals;
public class ShareContentProviderTest {
@Test
public void testCreateandParseURI_invalid() {
public void testCreateAndParseURI_invalid() {
Uri uri = Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/1");
Assert.assertArrayEquals(new long[]{}, ShareContentProvider.parseURI(uri));
assertEquals(0, ShareContentProvider.parseURI(uri).size());
}
@Test
public void testCreateandParseURI_valid() {
long[] trackIds = {1, 3, 5};
public void testCreateAndParseURI_valid() {
Set<Track.Id> trackIds = new HashSet<>();
trackIds.add(new Track.Id(1));
trackIds.add(new Track.Id(3));
trackIds.add(new Track.Id(5));
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertArrayEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
assertEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
}
@Test
public void testCreateURIescapeFilename() {
long[] trackIds = {1};
Set<Track.Id> trackIds = new HashSet<>();
trackIds.add(new Track.Id(1));
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_only_track/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_only_track/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
}
}
@@ -2,12 +2,14 @@ package de.dennisguse.opentracks.content.sensor;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.util.UintUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@RunWith(AndroidJUnit4.class)
public class SensorDataCyclingTest {
@@ -21,7 +23,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
assertEquals(60, current.getCadence_rpm(), 0.01);
}
@Test
@@ -34,7 +36,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
Assert.assertEquals(33.53, current.getCadence_rpm(), 0.01);
assertEquals(33.53, current.getCadence_rpm(), 0.01);
}
@Test
@@ -47,7 +49,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
Assert.assertEquals(0, current.getCadence_rpm(), 0.01);
assertEquals(0, current.getCadence_rpm(), 0.01);
}
@@ -61,7 +63,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
Assert.assertFalse(current.hasCadence_rpm());
assertFalse(current.hasCadence_rpm());
}
@Test
@@ -74,7 +76,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
assertEquals(60, current.getCadence_rpm(), 0.01);
}
@Test
@@ -87,7 +89,7 @@ public class SensorDataCyclingTest {
current.compute(previous);
// then
Assert.assertEquals(60, current.getCadence_rpm(), 0.01);
assertEquals(60, current.getCadence_rpm(), 0.01);
}
@Test
@@ -100,7 +102,7 @@ public class SensorDataCyclingTest {
current.compute(previous, 2150);
// then
Assert.assertEquals(1.20, current.getSpeed_mps(), 0.01);
assertEquals(1.20, current.getSpeed_mps(), 0.01);
}
@Test
@@ -113,6 +115,6 @@ public class SensorDataCyclingTest {
current.compute(previous, 2000);
// then
Assert.assertEquals(2, current.getSpeed_mps(), 0.01);
assertEquals(2, current.getSpeed_mps(), 0.01);
}
}
@@ -23,7 +23,6 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -35,6 +34,8 @@ import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.UnitConversions;
import static org.junit.Assert.assertEquals;
/**
* Tests {@link ChartFragment}.
*
@@ -78,9 +79,9 @@ public class ChartFragmentTest {
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
// then
Assert.assertEquals(Float.NaN, point.getHeartRate(), 0.01);
Assert.assertEquals(Float.NaN, point.getCadence(), 0.01);
Assert.assertEquals(Float.NaN, point.getPower(), 0.01);
assertEquals(Float.NaN, point.getHeartRate(), 0.01);
assertEquals(Float.NaN, point.getCadence(), 0.01);
assertEquals(Float.NaN, point.getPower(), 0.01);
}
/**
@@ -98,9 +99,9 @@ public class ChartFragmentTest {
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
// then
Assert.assertEquals(100.0, point.getHeartRate(), 0.01);
Assert.assertEquals(101.0, point.getCadence(), 0.01);
Assert.assertEquals(102.0, point.getPower(), 0.01);
assertEquals(100.0, point.getHeartRate(), 0.01);
assertEquals(101.0, point.getCadence(), 0.01);
assertEquals(102.0, point.getPower(), 0.01);
}
/**
@@ -112,12 +113,12 @@ public class ChartFragmentTest {
// Resets last location and writes first location.
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The second is a same location, just different time.
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The third location is a new location, and use metric.
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
@@ -129,7 +130,7 @@ public class ChartFragmentTest {
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(),
trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM;
Assert.assertEquals(distance1, point.getTimeOrDistance(), 0.01);
assertEquals(distance1, point.getTimeOrDistance(), 0.01);
// The fourth location is a new location, and use metric.
TrackPoint trackPoint4 = TrackStubUtils.createDefaultTrackPoint();
@@ -140,7 +141,7 @@ public class ChartFragmentTest {
Location.distanceBetween(trackPoint3.getLatitude(), trackPoint3.getLongitude(),
trackPoint4.getLatitude(), trackPoint4.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM;
Assert.assertEquals((distance1 + distance2), point.getTimeOrDistance(), 0.01);
assertEquals((distance1 + distance2), point.getTimeOrDistance(), 0.01);
}
/**
@@ -156,7 +157,7 @@ public class ChartFragmentTest {
// The first is a same location, just different time.
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
// The second location is a new location, and use imperial.
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
@@ -170,7 +171,7 @@ public class ChartFragmentTest {
float[] results = new float[4];
Location.distanceBetween(trackPoint1.getLatitude(), trackPoint1.getLongitude(), trackPoint2.getLatitude(), trackPoint2.getLongitude(), results);
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
Assert.assertEquals(distance1, point.getTimeOrDistance(), 0.01);
assertEquals(distance1, point.getTimeOrDistance(), 0.01);
// The third location is a new location, and use imperial.
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
@@ -183,7 +184,7 @@ public class ChartFragmentTest {
*/
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(), trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
Assert.assertEquals(distance1 + distance2, point.getTimeOrDistance(), 0.01);
assertEquals(distance1 + distance2, point.getTimeOrDistance(), 0.01);
}
/**
@@ -194,12 +195,12 @@ public class ChartFragmentTest {
chartFragment.setChartByDistance(false);
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getTimeOrDistance(), 0.01);
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
long timeSpan = 222;
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setTime(trackPoint1.getTime() + timeSpan);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals((double) timeSpan, point.getTimeOrDistance(), 0.01);
assertEquals((double) timeSpan, point.getTimeOrDistance(), 0.01);
}
/**
@@ -214,7 +215,7 @@ public class ChartFragmentTest {
* Then only one value INITIAL_ALTITUDE in buffer.
*/
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point.getElevation(), 0.01);
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point.getElevation(), 0.01);
/*
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
@@ -222,7 +223,7 @@ public class ChartFragmentTest {
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
trackPoint2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point.getElevation(), 0.01);
assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point.getElevation(), 0.01);
}
/**
@@ -239,7 +240,7 @@ public class ChartFragmentTest {
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(128.5f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getSpeed(), 0.01);
assertEquals(0.0, point.getSpeed(), 0.01);
/*
* Tests the logic when both metricUnits and reportSpeed are true.
@@ -253,7 +254,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint1.getTime() + 222);
trackPoint2.setSpeed(130);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01);
assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01);
}
/**
@@ -267,7 +268,7 @@ public class ChartFragmentTest {
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(100.0f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getSpeed(), 0.01);
assertEquals(0.0, point.getSpeed(), 0.01);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
@@ -278,7 +279,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
}
/**
@@ -292,7 +293,7 @@ public class ChartFragmentTest {
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
trackPoint1.setSpeed(100.0f);
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
Assert.assertEquals(0.0, point.getSpeed(), 0.01);
assertEquals(0.0, point.getSpeed(), 0.01);
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
@@ -303,7 +304,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01);
assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01);
}
/**
@@ -315,6 +316,6 @@ public class ChartFragmentTest {
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
trackPoint.setSpeed(0);
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
Assert.assertEquals(0.0, point.getPace(), 0.01);
assertEquals(0.0, point.getPace(), 0.01);
}
}
@@ -24,7 +24,6 @@ import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -39,6 +38,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;
@@ -96,11 +96,11 @@ public abstract class AbstractTestFileTrackImporter {
/**
* Expects the first track point to be added.
*
* @param trackPoint the trackPoint
* @param trackPoint the trackPoint
* @param trackId the track id
* @param trackPointId the track point id
*/
protected void expectFirstTrackPoint(TrackPoint trackPoint, long trackId, long trackPointId) {
protected void expectFirstTrackPoint(TrackPoint trackPoint, Track.Id trackId, long trackPointId) {
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(trackId))).thenReturn(1);
}
@@ -111,7 +111,7 @@ public abstract class AbstractTestFileTrackImporter {
* @param lastTrack true if it is the last track in the gpx
* @param trackId the track id
*/
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) {
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, Track.Id trackId) {
contentProviderUtils.updateTrack(trackCaptor.capture());
when(contentProviderUtils.insertWaypoint(any())).thenReturn(WAYPOINT_ID_O_URI);
@@ -122,10 +122,10 @@ public abstract class AbstractTestFileTrackImporter {
}
protected void verifyTrack(Track track, String name, String description, long time) {
Assert.assertEquals(name, track.getName());
Assert.assertEquals(description, track.getDescription());
assertEquals(name, track.getName());
assertEquals(description, track.getDescription());
if (time != -1L) {
Assert.assertEquals(time, track.getTrackStatistics().getStartTime_ms());
assertEquals(time, track.getTrackStatistics().getStartTime_ms());
}
}
}
@@ -56,8 +56,8 @@ public class ExportImportTest {
private final List<Waypoint> waypoints = new ArrayList<>();
private final List<TrackPoint> trackPoints = new ArrayList<>();
private long importTrackId;
private final long trackId = System.currentTimeMillis();
private Track.Id importTrackId;
private final Track.Id trackId = new Track.Id(System.currentTimeMillis());
@Before
public void setUp() {
@@ -15,7 +15,6 @@
*/
package de.dennisguse.opentracks.services;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -23,6 +22,8 @@ import org.junit.runners.JUnit4;
import de.dennisguse.opentracks.services.handlers.AdaptiveLocationListenerPolicy;
import static org.junit.Assert.assertEquals;
/**
* Tests the {@link AdaptiveLocationListenerPolicy}.
*
@@ -57,17 +58,17 @@ public class AdaptiveLocationListenerPolicyTest {
@Test
public void testGetDesiredPollingInterval() {
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_BIG);
Assert.assertEquals(MAX, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
assertEquals(MAX, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL);
// First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the nearest second.
Assert.assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
Assert.assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_LESS_THAN_MIN);
Assert.assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
}
/**
@@ -75,6 +76,6 @@ public class AdaptiveLocationListenerPolicyTest {
*/
@Test
public void testGetMinDistance() {
Assert.assertEquals(MIN_DISTANCE, adaptiveLocationListenerPolicy.getMinDistance_m());
assertEquals(MIN_DISTANCE, adaptiveLocationListenerPolicy.getMinDistance_m());
}
}
@@ -21,6 +21,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.IBinder;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -31,8 +32,8 @@ import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,6 +53,13 @@ import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Tests for the track recording service.
*
@@ -69,10 +77,17 @@ public class TrackRecordingServiceTest {
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private final long trackId = Math.abs(new Random().nextLong());
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
static Intent createStartIntent(Context context) {
return new Intent(context, TrackRecordingService.class);
@@ -111,22 +126,22 @@ public class TrackRecordingServiceTest {
@Test
public void testStartable() throws TimeoutException {
mServiceRule.startService(createStartIntent(context));
Assert.assertNotNull(mServiceRule.bindService(createStartIntent(context)));
assertNotNull(mServiceRule.bindService(createStartIntent(context)));
}
@MediumTest
@Test
public void testBindable() throws TimeoutException {
IBinder service = mServiceRule.bindService(createStartIntent(context));
Assert.assertNotNull(service);
assertNotNull(service);
}
@MediumTest
@Test
public void testRecording_noTracks() throws Exception {
// given
List<Track> tracks = contentProviderUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty());
List<Track> tracks = contentProviderUtils.getTracks();
assertTrue(tracks.isEmpty());
// when
Intent startIntent = createStartIntent(context);
@@ -135,8 +150,8 @@ public class TrackRecordingServiceTest {
// then
// Test if we start in no-recording mode by default.
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
}
@MediumTest
@@ -149,8 +164,8 @@ public class TrackRecordingServiceTest {
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
// then
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
}
@MediumTest
@@ -163,7 +178,7 @@ public class TrackRecordingServiceTest {
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
// then
Assert.assertTrue(service.isRecording());
assertTrue(service.isRecording());
}
@MediumTest
@@ -178,21 +193,21 @@ public class TrackRecordingServiceTest {
service.pauseCurrentTrack();
// then
Assert.assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
//when
service.resumeTrack(trackId);
newTrackPoint(service);
// then
Assert.assertTrue(service.isRecording());
Assert.assertEquals(trackId, service.getRecordingTrackId());
assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
Assert.assertEquals(5, trackPoints.size());
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
Assert.assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(3).getLatitude(), 0.01);
assertEquals(5, trackPoints.size());
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(3).getLatitude(), 0.01);
}
@MediumTest
@@ -204,20 +219,20 @@ public class TrackRecordingServiceTest {
newTrackPoint(service);
service.endCurrentTrack();
Assert.assertEquals(1, contentProviderUtils.getTrackPoints(trackId).size());
assertEquals(1, contentProviderUtils.getTrackPoints(trackId).size());
// when
service.resumeTrack(trackId);
newTrackPoint(service);
// then
Assert.assertTrue(service.isRecording());
Assert.assertEquals(trackId, service.getRecordingTrackId());
assertTrue(service.isRecording());
assertEquals(trackId, service.getRecordingTrackId());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
Assert.assertEquals(4, trackPoints.size());
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
Assert.assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
assertEquals(4, trackPoints.size());
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
}
@FlakyTest(detail = "Sometimes fails on CI.")
@@ -233,8 +248,8 @@ public class TrackRecordingServiceTest {
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
// then
Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
}
@MediumTest
@@ -243,18 +258,18 @@ public class TrackRecordingServiceTest {
// given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording());
assertTrue(service.isRecording());
long trackId = service.getRecordingTrackId();
Track.Id trackId = service.getRecordingTrackId();
// when
long newTrackId = service.startNewTrack();
Track.Id newTrackId = service.startNewTrack();
// then
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId);
assertNull(newTrackId);
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(trackId, service.getRecordingTrackId());
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
assertEquals(trackId, service.getRecordingTrackId());
}
@MediumTest
@@ -262,15 +277,15 @@ public class TrackRecordingServiceTest {
public void testEndCurrentTrack_noRecording() throws Exception {
// given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
assertFalse(service.isRecording());
// when
// Ending the current track when there is no recording should not result in any error.
service.endCurrentTrack();
// then
Assert.assertFalse(PreferencesUtils.isRecording(context));
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId());
assertFalse(PreferencesUtils.isRecording(context));
assertNull(service.getRecordingTrackId());
}
@MediumTest
@@ -278,13 +293,13 @@ public class TrackRecordingServiceTest {
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
// given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
Assert.assertFalse(service.isRecording());
assertFalse(service.isRecording());
// when
long waypointId = service.insertWaypoint(null, null, null, null);
Waypoint.Id waypointId = service.insertWaypoint(null, null, null, null);
// then
Assert.assertEquals(-1L, waypointId);
assertNull(waypointId);
}
@MediumTest
@@ -293,35 +308,35 @@ public class TrackRecordingServiceTest {
// given
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
service.startNewTrack();
Assert.assertTrue(service.isRecording());
assertTrue(service.isRecording());
newTrackPoint(service);
long trackId = service.getRecordingTrackId();
Track.Id trackId = service.getRecordingTrackId();
// when
long waypointId = service.insertWaypoint(null, null, null, null);
Waypoint.Id waypointId = service.insertWaypoint(null, null, null, null);
// then
Assert.assertNotEquals(-1L, waypointId);
assertNotEquals(-1L, waypointId);
Waypoint wpt = contentProviderUtils.getWaypoint(waypointId);
Assert.assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
Assert.assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
Assert.assertEquals(trackId, wpt.getTrackId());
Assert.assertEquals(0.0, wpt.getLength(), 0.01);
Assert.assertNotNull(wpt.getLocation());
assertEquals(context.getString(R.string.marker_waypoint_icon_url), wpt.getIcon());
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
assertEquals(trackId, wpt.getTrackId());
assertEquals(0.0, wpt.getLength(), 0.01);
assertNotNull(wpt.getLocation());
service.endCurrentTrack();
}
private void addTrack(Track track, boolean isRecording) {
Assert.assertTrue(track.getId() >= 0);
assertTrue(track.getId().isValid());
contentProviderUtils.insertTrack(track);
Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
}
// NOTE: Do not use to create a track that is currently recording.
private void createDummyTrack(long id, long stopTime, boolean isRecording) {
private void createDummyTrack(Track.Id id, long stopTime, boolean isRecording) {
Track dummyTrack = new Track();
dummyTrack.setId(id);
dummyTrack.setName("Dummy Track");
@@ -12,7 +12,6 @@ import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
@@ -23,6 +22,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -32,6 +32,9 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* Tests insert location.
* <p>
@@ -89,7 +92,7 @@ public class TrackRecordingServiceTestLocation {
@Test
public void testOnLocationChangedAsync_movingAccurate() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
@@ -102,18 +105,18 @@ public class TrackRecordingServiceTestLocation {
service.endCurrentTrack();
// then
Assert.assertFalse(service.isRecording());
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
Assert.assertEquals(6, trackPoints.size());
Assert.assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
assertEquals(6, trackPoints.size());
assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
@@ -126,11 +129,11 @@ public class TrackRecordingServiceTestLocation {
service.endCurrentTrack();
// then
Assert.assertFalse(service.isRecording());
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
Assert.assertEquals(2, trackPoints.size());
Assert.assertEquals(45.000005, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(2, trackPoints.size());
assertEquals(45.000005, trackPoints.get(1).getLatitude(), 0.01);
}
// @MediumTest
@@ -155,7 +158,7 @@ public class TrackRecordingServiceTestLocation {
@Test
public void testOnLocationChangedAsync_idle() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
@@ -168,20 +171,20 @@ public class TrackRecordingServiceTestLocation {
service.endCurrentTrack();
// then
Assert.assertFalse(service.isRecording());
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
Assert.assertEquals(3, trackPoints.size());
Assert.assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
Assert.assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
Assert.assertEquals(6, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(3, trackPoints.size());
assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(6, trackPoints.get(2).getAccuracy(), 0.01);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_idle_withMovement() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
@@ -194,14 +197,14 @@ public class TrackRecordingServiceTestLocation {
service.endCurrentTrack();
// then
Assert.assertFalse(service.isRecording());
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
Assert.assertEquals(4, trackPoints.size());
Assert.assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
Assert.assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
Assert.assertEquals(5, trackPoints.get(2).getAccuracy(), 0.01); //TODO Check why this trackPoint is inserted.
Assert.assertEquals(6, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(4, trackPoints.size());
assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(2).getAccuracy(), 0.01); //TODO Check why this trackPoint is inserted.
assertEquals(6, trackPoints.get(3).getAccuracy(), 0.01);
}
@@ -209,7 +212,7 @@ public class TrackRecordingServiceTestLocation {
@Test
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
service.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
@@ -237,23 +240,23 @@ public class TrackRecordingServiceTestLocation {
service.endCurrentTrack();
// then
Assert.assertFalse(service.isRecording());
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
Assert.assertEquals(6, trackPoints.size());
Assert.assertEquals(0, trackPoints.get(0).getAccuracy(), 0.01);
Assert.assertEquals(1, trackPoints.get(1).getAccuracy(), 0.01);
Assert.assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
Assert.assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
Assert.assertEquals(4, trackPoints.get(4).getAccuracy(), 0.01);
Assert.assertEquals(5, trackPoints.get(5).getAccuracy(), 0.01);
assertEquals(6, trackPoints.size());
assertEquals(0, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(1, trackPoints.get(1).getAccuracy(), 0.01);
assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(4, trackPoints.get(4).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(5).getAccuracy(), 0.01);
}
@MediumTest
@Test
public void testOnLocationChangedAsync_segment() throws Exception {
// given
long trackId = service.startNewTrack();
Track.Id trackId = service.startNewTrack();
// when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
@@ -265,16 +268,16 @@ public class TrackRecordingServiceTestLocation {
service.endCurrentTrack();
// then
Assert.assertFalse(service.isRecording());
assertFalse(service.isRecording());
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
Assert.assertEquals(7, trackPoints.size());
Assert.assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
Assert.assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
Assert.assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
Assert.assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(4).getLatitude(), 0.01);
Assert.assertEquals(4, trackPoints.get(5).getAccuracy(), 0.01);
Assert.assertEquals(5, trackPoints.get(6).getAccuracy(), 0.01);
assertEquals(7, trackPoints.size());
assertEquals(1, trackPoints.get(0).getAccuracy(), 0.01);
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
assertEquals(2, trackPoints.get(2).getAccuracy(), 0.01);
assertEquals(3, trackPoints.get(3).getAccuracy(), 0.01);
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(4).getLatitude(), 0.01);
assertEquals(4, trackPoints.get(5).getAccuracy(), 0.01);
assertEquals(5, trackPoints.get(6).getAccuracy(), 0.01);
}
}
@@ -13,7 +13,6 @@ import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
@@ -32,6 +31,12 @@ import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Tests for the track recording service, which require a {@link Looper}.
*
@@ -187,24 +192,24 @@ public class TrackRecordingServiceTestLooper {
@MediumTest
@Test
public void testIntegration_completeRecordingSession() throws TimeoutException {
List<Track> tracks = contentProviderUtils.getAllTracks();
Assert.assertTrue(tracks.isEmpty());
List<Track> tracks = contentProviderUtils.getTracks();
assertTrue(tracks.isEmpty());
fullRecordingSession();
}
private void fullRecordingSession() throws TimeoutException {
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)));
Assert.assertFalse(service.isRecording());
assertFalse(service.isRecording());
// Start a track.
long id = service.startNewTrack();
Assert.assertTrue(id >= 0);
Assert.assertTrue(service.isRecording());
Track track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(id, service.getRecordingTrackId());
Track.Id trackId = service.startNewTrack();
assertTrue(trackId.isValid());
assertTrue(service.isRecording());
Track track = contentProviderUtils.getTrack(trackId);
assertNotNull(track);
assertEquals(trackId, track.getId());
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
assertEquals(trackId, service.getRecordingTrackId());
// Insert a few points, markers and statistics.
long startTime = System.currentTimeMillis();
@@ -227,14 +232,14 @@ public class TrackRecordingServiceTestLooper {
// Stop the track. Validate if it has correct data.
service.endCurrentTrack();
Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId());
track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId());
assertFalse(service.isRecording());
assertNull(service.getRecordingTrackId());
track = contentProviderUtils.getTrack(trackId);
assertNotNull(track);
assertEquals(trackId, track.getId());
TrackStatistics trackStatistics = track.getTrackStatistics();
Assert.assertNotNull(trackStatistics);
Assert.assertTrue(trackStatistics.getStartTime_ms() > 0);
Assert.assertTrue(trackStatistics.getStopTime_ms() >= trackStatistics.getStartTime_ms());
assertNotNull(trackStatistics);
assertTrue(trackStatistics.getStartTime_ms() > 0);
assertTrue(trackStatistics.getStopTime_ms() >= trackStatistics.getStartTime_ms());
}
}
@@ -23,11 +23,12 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link AnnouncementPeriodicTaskFactory}.
*
@@ -53,6 +54,6 @@ public class AnnouncementPeriodicTaskFactoryTest {
public void testCreate() {
PeriodicTaskFactory factory = new AnnouncementPeriodicTaskFactory();
PeriodicTask task = factory.create(context);
Assert.assertTrue(task instanceof AnnouncementPeriodicTask);
assertTrue(task instanceof AnnouncementPeriodicTask);
}
}
@@ -15,12 +15,13 @@
*/
package de.dennisguse.opentracks.stats;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link TrackStatistics}.
* This only tests non-trivial pieces of that class.
@@ -61,28 +62,28 @@ public class TrackStatisticsTest {
statistics.merge(statistics2);
Assert.assertEquals(1000L, statistics.getStartTime_ms());
Assert.assertEquals(4000L, statistics.getStopTime_ms());
Assert.assertEquals(2500L, statistics.getTotalTime());
Assert.assertEquals(1300L, statistics.getMovingTime());
Assert.assertEquals(1100.0, statistics.getTotalDistance(), 0.001);
Assert.assertEquals(900.0, statistics.getTotalElevationGain(), 0.001);
Assert.assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed(), 0.001);
Assert.assertEquals(1200.0, statistics.getMinElevation(), 0.001);
Assert.assertEquals(3575.0, statistics.getMaxElevation(), 0.001);
assertEquals(1000L, statistics.getStartTime_ms());
assertEquals(4000L, statistics.getStopTime_ms());
assertEquals(2500L, statistics.getTotalTime());
assertEquals(1300L, statistics.getMovingTime());
assertEquals(1100.0, statistics.getTotalDistance(), 0.001);
assertEquals(900.0, statistics.getTotalElevationGain(), 0.001);
assertEquals(statistics.getTotalDistance() / (statistics.getMovingTime() / 1000.0), statistics.getMaxSpeed(), 0.001);
assertEquals(1200.0, statistics.getMinElevation(), 0.001);
assertEquals(3575.0, statistics.getMaxElevation(), 0.001);
}
@Test
public void testGetAverageSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setTotalTime(50000); // in milliseconds
Assert.assertEquals(20.0, statistics.getAverageSpeed(), 0.001);
assertEquals(20.0, statistics.getAverageSpeed(), 0.001);
}
@Test
public void testGetAverageMovingSpeed() {
statistics.setTotalDistance(1000.0);
statistics.setMovingTime(20000); // in milliseconds
Assert.assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001);
assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001);
}
}
@@ -5,11 +5,20 @@ import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class AnnouncementUtilsTest {
@@ -26,9 +35,50 @@ public class AnnouncementUtilsTest {
stats.setTotalElevationGain(6000);
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane");
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", null);
// then
Assert.assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
}
@Test
public void getAnnouncement_withInterval() {
TrackStatistics stats = new TrackStatistics();
stats.setTotalDistance(20000);
stats.setTotalTime(600000);
stats.setMovingTime(300000);
stats.setMaxSpeed(100);
stats.setTotalElevationGain(6000);
IntervalStatistics intervalStatistics = new IntervalStatistics();
List<TrackPoint> trackPoints = Arrays.asList(TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second);
intervalStatistics.build(trackPoints, 1000);
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
int speedId = R.plurals.voiceSpeedKilometersPerHour;
double kmPerHour = lastInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH;
String firstPartMsg = "OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour";
String rateMsg = " With a current kilometer speed of " + context.getResources().getQuantityString(speedId, getQuantityCount(kmPerHour), kmPerHour);
String msg = firstPartMsg + rateMsg;
// when
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", lastInterval);
// then
assertEquals(msg, announcement);
}
private int getQuantityCount(double d) {
if (d == 0) {
return 0;
} else if (d == 1) {
return 1;
} else if (d == 2) {
return 2;
} else {
int count = (int) d;
return Math.max(count, 3);
}
}
}
@@ -2,11 +2,13 @@ package de.dennisguse.opentracks.util;
import android.bluetooth.BluetoothGattCharacteristic;
import org.junit.Assert;
import org.junit.Test;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class BluetoothUtilsTest {
@Test
@@ -19,7 +21,7 @@ public class BluetoothUtilsTest {
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
// then
Assert.assertEquals(60, heartRate);
assertEquals(60, heartRate);
}
@Test
@@ -32,7 +34,7 @@ public class BluetoothUtilsTest {
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
// then
Assert.assertEquals(257, heartRate);
assertEquals(257, heartRate);
}
@Test
@@ -44,8 +46,8 @@ public class BluetoothUtilsTest {
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
Assert.assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
Assert.assertNull(sensor.getSpeed());
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertNull(sensor.getSpeed());
}
@Test
@@ -57,8 +59,8 @@ public class BluetoothUtilsTest {
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
Assert.assertNull(sensor.getCadence());
Assert.assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
assertNull(sensor.getCadence());
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
}
@Test
@@ -70,7 +72,7 @@ public class BluetoothUtilsTest {
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
// then
Assert.assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
Assert.assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
}
}
@@ -15,7 +15,6 @@
*/
package de.dennisguse.opentracks.util;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -23,6 +22,8 @@ import org.junit.runners.JUnit4;
import java.io.File;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link FileUtils}.
*
@@ -37,7 +38,7 @@ public class FileUtilsTest {
@Test
public void testBuildUniqueFileName_new() {
String filename = FileUtils.buildUniqueFileName(new File("/dir"), "Filename", "ext");
Assert.assertEquals("Filename.ext", filename);
assertEquals("Filename.ext", filename);
}
/**
@@ -47,7 +48,7 @@ public class FileUtilsTest {
public void testBuildUniqueFileName_exist() {
// Expect "/default.prop" to exist on the phone/emulator
String filename = FileUtils.buildUniqueFileName(new File("/"), "default", "prop");
Assert.assertEquals("default(1).prop", filename);
assertEquals("default(1).prop", filename);
}
/**
@@ -58,13 +59,13 @@ public class FileUtilsTest {
public void testSanitizeFileName() {
String name = "Swim\10ming-^across:/the/ pacific (ocean).";
String expected = "Swim_ming-^across_the_ pacific (ocean).";
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
assertEquals(expected, FileUtils.sanitizeFileName(name));
}
@Test
public void testSanitizeFileNameWithSuffix() {
String name = "FileName.jpeg";
Assert.assertEquals(name, FileUtils.sanitizeFileName(name));
assertEquals(name, FileUtils.sanitizeFileName(name));
}
/**
@@ -75,7 +76,7 @@ public class FileUtilsTest {
public void testSanitizeFileName_i18n() {
String name = "您好-привет";
String expected = "您好-привет";
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
@@ -86,7 +87,7 @@ public class FileUtilsTest {
public void testSanitizeFileName_special_characters() {
String name = "$%'-_@~`!(){}^#&+,;=[] ";
String expected = "$%'-_@~`!(){}^#&+,;=[] ";
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
@@ -97,7 +98,7 @@ public class FileUtilsTest {
public void testSanitizeFileName_collapse() {
String name = "hello//there";
String expected = "hello_there";
Assert.assertEquals(expected, FileUtils.sanitizeFileName(name));
assertEquals(expected, FileUtils.sanitizeFileName(name));
}
/**
@@ -114,8 +115,8 @@ public class FileUtilsTest {
String truncated = FileUtils.truncateFileName(directory, nameString, suffix);
for (int i = 0; i < truncated.length(); i++) {
Assert.assertEquals('a', truncated.charAt(i));
assertEquals('a', truncated.charAt(i));
}
Assert.assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, new File(directory, truncated + suffix).getPath().length());
assertEquals(FileUtils.MAX_FAT32_PATH_LENGTH, new File(directory, truncated + suffix).getPath().length());
}
}
@@ -6,13 +6,14 @@ import android.content.SharedPreferences;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class PreferencesUtilsTest {
@@ -29,7 +30,7 @@ public class PreferencesUtilsTest {
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
// then
Assert.assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
}
@Test
@@ -43,7 +44,7 @@ public class PreferencesUtilsTest {
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
// then
Assert.assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
@Test
@@ -57,6 +58,6 @@ public class PreferencesUtilsTest {
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
// then
Assert.assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
}
@@ -21,13 +21,15 @@ import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link StringUtils}.
*
@@ -44,7 +46,7 @@ public class StringUtilsTest {
*/
@Test
public void testFormatDateTimeIso8601() {
Assert.assertEquals("1970-01-01T00:00:12.345Z", StringUtils.formatDateTimeIso8601(12345));
assertEquals("1970-01-01T00:00:12.345Z", StringUtils.formatDateTimeIso8601(12345));
}
/**
@@ -53,19 +55,19 @@ public class StringUtilsTest {
@Test
public void testformatElapsedTime() {
// 1 second
Assert.assertEquals("00:01", StringUtils.formatElapsedTime(1000));
assertEquals("00:01", StringUtils.formatElapsedTime(1000));
// 10 seconds
Assert.assertEquals("00:10", StringUtils.formatElapsedTime(10000));
assertEquals("00:10", StringUtils.formatElapsedTime(10000));
// 1 minute
Assert.assertEquals("01:00", StringUtils.formatElapsedTime(60000));
assertEquals("01:00", StringUtils.formatElapsedTime(60000));
// 10 minutes
Assert.assertEquals("10:00", StringUtils.formatElapsedTime(600000));
assertEquals("10:00", StringUtils.formatElapsedTime(600000));
// 1 hour
Assert.assertEquals("1:00:00", StringUtils.formatElapsedTime(3600000));
assertEquals("1:00:00", StringUtils.formatElapsedTime(3600000));
// 10 hours
Assert.assertEquals("10:00:00", StringUtils.formatElapsedTime(36000000));
assertEquals("10:00:00", StringUtils.formatElapsedTime(36000000));
// 100 hours
Assert.assertEquals("100:00:00", StringUtils.formatElapsedTime(360000000));
assertEquals("100:00:00", StringUtils.formatElapsedTime(360000000));
}
/**
@@ -74,19 +76,19 @@ public class StringUtilsTest {
@Test
public void testformatElapsedTimeWithHour() {
// 1 second
Assert.assertEquals("0:00:01", StringUtils.formatElapsedTimeWithHour(1000));
assertEquals("0:00:01", StringUtils.formatElapsedTimeWithHour(1000));
// 10 seconds
Assert.assertEquals("0:00:10", StringUtils.formatElapsedTimeWithHour(10000));
assertEquals("0:00:10", StringUtils.formatElapsedTimeWithHour(10000));
// 1 minute
Assert.assertEquals("0:01:00", StringUtils.formatElapsedTimeWithHour(60000));
assertEquals("0:01:00", StringUtils.formatElapsedTimeWithHour(60000));
// 10 minutes
Assert.assertEquals("0:10:00", StringUtils.formatElapsedTimeWithHour(600000));
assertEquals("0:10:00", StringUtils.formatElapsedTimeWithHour(600000));
// 1 hour
Assert.assertEquals("1:00:00", StringUtils.formatElapsedTimeWithHour(3600000));
assertEquals("1:00:00", StringUtils.formatElapsedTimeWithHour(3600000));
// 10 hours
Assert.assertEquals("10:00:00", StringUtils.formatElapsedTimeWithHour(36000000));
assertEquals("10:00:00", StringUtils.formatElapsedTimeWithHour(36000000));
// 100 hours
Assert.assertEquals("100:00:00", StringUtils.formatElapsedTimeWithHour(360000000));
assertEquals("100:00:00", StringUtils.formatElapsedTimeWithHour(360000000));
}
/**
@@ -96,13 +98,13 @@ public class StringUtilsTest {
@Test
public void testFormatDistance() {
// A large number in metric
Assert.assertEquals("5.00 km", StringUtils.formatDistance(context, 5000, true));
assertEquals("5.00 km", StringUtils.formatDistance(context, 5000, true));
// A large number in imperial
Assert.assertEquals("3.11 mi", StringUtils.formatDistance(context, 5000, false));
assertEquals("3.11 mi", StringUtils.formatDistance(context, 5000, false));
// A small number in metric
Assert.assertEquals("100.00 m", StringUtils.formatDistance(context, 100, true));
assertEquals("100.00 m", StringUtils.formatDistance(context, 100, true));
// A small number in imperial
Assert.assertEquals("328.08 ft", StringUtils.formatDistance(context, 100, false));
assertEquals("328.08 ft", StringUtils.formatDistance(context, 100, false));
}
/**
@@ -110,8 +112,8 @@ public class StringUtilsTest {
*/
@Test
public void testFormatCData() {
Assert.assertEquals("<![CDATA[hello]]>", StringUtils.formatCData("hello"));
Assert.assertEquals("<![CDATA[hello]]]]><![CDATA[>there]]>", StringUtils.formatCData("hello]]>there"));
assertEquals("<![CDATA[hello]]>", StringUtils.formatCData("hello"));
assertEquals("<![CDATA[hello]]]]><![CDATA[>there]]>", StringUtils.formatCData("hello]]>there"));
}
/**
@@ -168,7 +170,7 @@ public class StringUtilsTest {
// This comparision tends to be flaky (difference of 1ms)
// Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.parseTime(xmlDateTime));
Assert.assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime)) <= 1);
assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime)) <= 1);
}
/**
@@ -177,9 +179,9 @@ public class StringUtilsTest {
@Test
public void testGetTimeParts_positive() {
int[] parts = StringUtils.getTimeParts(61000);
Assert.assertEquals(1, parts[0]);
Assert.assertEquals(1, parts[1]);
Assert.assertEquals(0, parts[2]);
assertEquals(1, parts[0]);
assertEquals(1, parts[1]);
assertEquals(0, parts[2]);
}
/**
@@ -188,40 +190,40 @@ public class StringUtilsTest {
@Test
public void testGetTimeParts_negative() {
int[] parts = StringUtils.getTimeParts(-61000);
Assert.assertEquals(-1, parts[0]);
Assert.assertEquals(-1, parts[1]);
Assert.assertEquals(0, parts[2]);
assertEquals(-1, parts[0]);
assertEquals(-1, parts[1]);
assertEquals(0, parts[2]);
}
@Test
public void testFormatDecimal() {
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 0));
Assert.assertEquals("0", StringUtils.formatDecimal(0.1, 0));
Assert.assertEquals("1", StringUtils.formatDecimal(1.1, 0));
Assert.assertEquals("10", StringUtils.formatDecimal(10, 0));
Assert.assertEquals("10", StringUtils.formatDecimal(10.1, 0));
Assert.assertEquals("0", StringUtils.formatDecimal(-0.1, 0));
assertEquals("0", StringUtils.formatDecimal(0.0, 0));
assertEquals("0", StringUtils.formatDecimal(0.1, 0));
assertEquals("1", StringUtils.formatDecimal(1.1, 0));
assertEquals("10", StringUtils.formatDecimal(10, 0));
assertEquals("10", StringUtils.formatDecimal(10.1, 0));
assertEquals("0", StringUtils.formatDecimal(-0.1, 0));
Assert.assertEquals("0", StringUtils.formatDecimal(0.0, 2));
Assert.assertEquals("0.1", StringUtils.formatDecimal(0.1, 2));
Assert.assertEquals("1.1", StringUtils.formatDecimal(1.1, 2));
Assert.assertEquals("10", StringUtils.formatDecimal(10, 2));
Assert.assertEquals("10.1", StringUtils.formatDecimal(10.1, 2));
Assert.assertEquals("10.11", StringUtils.formatDecimal(10.111, 2));
Assert.assertEquals("-0.1", StringUtils.formatDecimal(-0.1, 2));
assertEquals("0", StringUtils.formatDecimal(0.0, 2));
assertEquals("0.1", StringUtils.formatDecimal(0.1, 2));
assertEquals("1.1", StringUtils.formatDecimal(1.1, 2));
assertEquals("10", StringUtils.formatDecimal(10, 2));
assertEquals("10.1", StringUtils.formatDecimal(10.1, 2));
assertEquals("10.11", StringUtils.formatDecimal(10.111, 2));
assertEquals("-0.1", StringUtils.formatDecimal(-0.1, 2));
Assert.assertEquals("1", StringUtils.formatDecimal(0.99, 1));
assertEquals("1", StringUtils.formatDecimal(0.99, 1));
}
@Test
public void testGetSpeedParts() {
Assert.assertEquals("4:59", StringUtils.getSpeedParts(context, 3.34, true, false).first);
Assert.assertEquals("5:00", StringUtils.getSpeedParts(context, 3.33, true, false).first);
assertEquals("4:59", StringUtils.getSpeedParts(context, 3.34, true, false).first);
assertEquals("5:00", StringUtils.getSpeedParts(context, 3.33, true, false).first);
Assert.assertEquals("11.99", StringUtils.getSpeedParts(context, 3.33, true, true).first);
Assert.assertEquals("7.45", StringUtils.getSpeedParts(context, 3.33, false, true).first);
assertEquals("11.99", StringUtils.getSpeedParts(context, 3.33, true, true).first);
assertEquals("7.45", StringUtils.getSpeedParts(context, 3.33, false, true).first);
Assert.assertEquals("min/km", StringUtils.getSpeedParts(context, 0, true, false).second);
Assert.assertEquals("min/mi", StringUtils.getSpeedParts(context, 0, false, false).second);
assertEquals("min/km", StringUtils.getSpeedParts(context, 0, true, false).second);
assertEquals("min/mi", StringUtils.getSpeedParts(context, 0, false, false).second);
}
}
@@ -21,7 +21,6 @@ import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,6 +29,9 @@ import java.util.Date;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import static org.junit.Assert.assertEquals;
/**
* Tests {@link TrackNameUtils}.
@@ -39,7 +41,7 @@ import de.dennisguse.opentracks.R;
@RunWith(AndroidJUnit4.class)
public class TrackNameUtilsTest {
private static final long TRACK_ID = 1L;
private static final Track.Id TRACK_ID = new Track.Id(1L);
private static final long START_TIME = 1288213406000L;
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
@@ -50,7 +52,7 @@ public class TrackNameUtilsTest {
@Test
public void testTrackName_date_local() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
Assert.assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
/**
@@ -60,7 +62,7 @@ public class TrackNameUtilsTest {
public void testTrackName_date_iso_8601() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
Assert.assertEquals(simpleDateFormat.format(new Date(START_TIME)), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
assertEquals(simpleDateFormat.format(new Date(START_TIME)), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
/**
@@ -69,6 +71,6 @@ public class TrackNameUtilsTest {
@Test
public void testTrackName_number() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
Assert.assertEquals("Track " + TRACK_ID, TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
}
}
@@ -1,10 +1,11 @@
package de.dennisguse.opentracks.util;
import org.junit.Assert;
import org.junit.Test;
import de.dennisguse.opentracks.content.data.TrackPoint;
import static org.junit.Assert.assertEquals;
public class TrackPointUtilsTest {
@Test
@@ -18,7 +19,7 @@ public class TrackPointUtilsTest {
TrackPointUtils.fixTime(trackPoint);
// then
Assert.assertEquals(time, trackPoint.getTime());
assertEquals(time, trackPoint.getTime());
}
@Test
@@ -32,7 +33,7 @@ public class TrackPointUtilsTest {
TrackPointUtils.fixTime(trackPoint);
// then
Assert.assertEquals(time, trackPoint.getTime(), 1000);
assertEquals(time, trackPoint.getTime(), 1000);
}
@Test
@@ -46,7 +47,7 @@ public class TrackPointUtilsTest {
TrackPointUtils.fixTime(trackPoint);
// then
Assert.assertEquals(time, trackPoint.getTime());
assertEquals(time, trackPoint.getTime());
}
}
@@ -2,12 +2,13 @@ package de.dennisguse.opentracks.util;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class UUIDUtilsTest {
@@ -18,6 +19,6 @@ public class UUIDUtilsTest {
byte[] bytes = UUIDUtils.toBytes(uuid);
UUID output = UUIDUtils.fromBytes(bytes);
Assert.assertEquals(uuid, output);
assertEquals(uuid, output);
}
}
@@ -4,7 +4,6 @@ import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -14,6 +13,9 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.TrackIconUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(JUnit4.class)
public class AggregatedStatisticsTest {
@@ -59,13 +61,13 @@ public class AggregatedStatisticsTest {
aggregatedStatistics.aggregate(track);
// then
Assert.assertEquals(1, aggregatedStatistics.getCount());
Assert.assertNotNull(aggregatedStatistics.get(biking));
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
assertEquals(1, aggregatedStatistics.getCount());
assertNotNull(aggregatedStatistics.get(biking));
assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
assertEquals(totalTime, statistics2.getMovingTime());
}
@Test
@@ -82,7 +84,7 @@ public class AggregatedStatisticsTest {
aggregatedStatistics.aggregate(track);
// then
Assert.assertNotNull(aggregatedStatistics.get(mountainBiking));
assertNotNull(aggregatedStatistics.get(mountainBiking));
}
@Test
@@ -99,7 +101,7 @@ public class AggregatedStatisticsTest {
aggregatedStatistics.aggregate(track);
// then
Assert.assertNotNull(aggregatedStatistics.get(trailRunning));
assertNotNull(aggregatedStatistics.get(trailRunning));
}
@Test
@@ -120,13 +122,13 @@ public class AggregatedStatisticsTest {
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
// then
Assert.assertEquals(1, aggregatedStatistics.getCount());
Assert.assertNotNull(aggregatedStatistics.get(biking));
Assert.assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
assertEquals(1, aggregatedStatistics.getCount());
assertNotNull(aggregatedStatistics.get(biking));
assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime * 2L, statistics2.getMovingTime());
assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
assertEquals(totalTime * 2L, statistics2.getMovingTime());
}
@Test
@@ -152,30 +154,30 @@ public class AggregatedStatisticsTest {
aggregatedStatistics.aggregate(tracks[2]); // biking activity 3.
// then
Assert.assertEquals(3, aggregatedStatistics.getCount());
Assert.assertNotNull(aggregatedStatistics.get(biking));
Assert.assertNotNull(aggregatedStatistics.get(running));
Assert.assertNotNull(aggregatedStatistics.get(walking));
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
Assert.assertEquals(1, aggregatedStatistics.get(running).getCountTracks());
Assert.assertEquals(1, aggregatedStatistics.get(walking).getCountTracks());
assertEquals(3, aggregatedStatistics.getCount());
assertNotNull(aggregatedStatistics.get(biking));
assertNotNull(aggregatedStatistics.get(running));
assertNotNull(aggregatedStatistics.get(walking));
assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
assertEquals(1, aggregatedStatistics.get(running).getCountTracks());
assertEquals(1, aggregatedStatistics.get(walking).getCountTracks());
{
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
assertEquals(totalTime, statistics2.getMovingTime());
}
{
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
assertEquals(totalTime, statistics2.getMovingTime());
}
{
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
assertEquals(totalTime, statistics2.getMovingTime());
}
}
@@ -212,46 +214,46 @@ public class AggregatedStatisticsTest {
// then
// 4 sports.
Assert.assertEquals(4, aggregatedStatistics.getCount());
assertEquals(4, aggregatedStatistics.getCount());
// There is a map for every sport.
Assert.assertNotNull(aggregatedStatistics.get(biking));
Assert.assertNotNull(aggregatedStatistics.get(running));
Assert.assertNotNull(aggregatedStatistics.get(walking));
Assert.assertNotNull(aggregatedStatistics.get(driving));
assertNotNull(aggregatedStatistics.get(biking));
assertNotNull(aggregatedStatistics.get(running));
assertNotNull(aggregatedStatistics.get(walking));
assertNotNull(aggregatedStatistics.get(driving));
// Number of tracks by sport.
Assert.assertEquals(5, aggregatedStatistics.get(biking).getCountTracks()); // Biking.
Assert.assertEquals(2, aggregatedStatistics.get(running).getCountTracks()); // Running.
Assert.assertEquals(2, aggregatedStatistics.get(walking).getCountTracks()); // Walking.
Assert.assertEquals(1, aggregatedStatistics.get(driving).getCountTracks()); // Driving.
assertEquals(5, aggregatedStatistics.get(biking).getCountTracks()); // Biking.
assertEquals(2, aggregatedStatistics.get(running).getCountTracks()); // Running.
assertEquals(2, aggregatedStatistics.get(walking).getCountTracks()); // Walking.
assertEquals(1, aggregatedStatistics.get(driving).getCountTracks()); // Driving.
// Biking.
{
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
Assert.assertEquals(totalDistance * 5, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime * 5, statistics2.getMovingTime());
assertEquals(totalDistance * 5, statistics2.getTotalDistance(), 0);
assertEquals(totalTime * 5, statistics2.getMovingTime());
}
// Running.
{
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime * 2, statistics2.getMovingTime());
assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
assertEquals(totalTime * 2, statistics2.getMovingTime());
}
// Walking.
{
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime * 2, statistics2.getMovingTime());
assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
assertEquals(totalTime * 2, statistics2.getMovingTime());
}
// Driving.
{
TrackStatistics statistics2 = aggregatedStatistics.get(driving).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
assertEquals(totalTime, statistics2.getMovingTime());
}
}
}
@@ -0,0 +1,144 @@
package de.dennisguse.opentracks.viewmodels;
import android.util.Pair;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.UnitConversions;
import static org.junit.Assert.assertEquals;
@RunWith(JUnit4.class)
public class IntervalStatisticsTest {
private static final String TAG = IntervalStatisticsTest.class.getSimpleName();
private List<TrackPoint> buildTrackPoints(int numberOfTrackPoints) {
Pair<Track, TrackPoint[]> pair = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints);
return Arrays.asList(pair.second);
}
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(trackPoints.get(0).getTime());
for (TrackPoint tp : trackPoints) {
trackStatisticsUpdater.addTrackPoint(tp, 0);
}
return trackStatisticsUpdater.getTrackStatistics();
}
/**
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
*/
@Test
public void testBuild_1() {
// With 50 points and interval distance of 1000m.
// given
List<TrackPoint> trackPoints = buildTrackPoints(50);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 1000f;
IntervalStatistics intervalStatistics = new IntervalStatistics();
// when and then
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
}
/**
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
*/
@Test
public void testBuild_2() {
// With 200 points and interval distance of 1000m.
// given
List<TrackPoint> trackPoints = buildTrackPoints(200);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 1000f;
IntervalStatistics intervalStatistics = new IntervalStatistics();
// when and then
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
}
/**
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
*/
@Test
public void testBuild_3() {
// With 200 points and interval distance of 3000m.
// given
List<TrackPoint> trackPoints = buildTrackPoints(200);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 3000;
IntervalStatistics intervalStatistics = new IntervalStatistics();
// when and then
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
}
/**
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
*/
@Test
public void testBuild_4() {
// With 1000 points and interval distance of 3000m.
// given
List<TrackPoint> trackPoints = buildTrackPoints(1000);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 3000;
IntervalStatistics intervalStatistics = new IntervalStatistics();
// when and then
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
}
/**
* Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result.
*/
@Test
public void testBuild_5() {
// With 10000 points and interval distance of 1000m.
// given
List<TrackPoint> trackPoints = buildTrackPoints(10000);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 1000;
IntervalStatistics intervalStatistics = new IntervalStatistics();
// when and then
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
}
private void whenAndThen(List<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval, IntervalStatistics intervalStatistics) {
intervalStatistics.build(trackPoints, distanceInterval);
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
double totalDistance = 0d;
long totalTime = 0L;
for (IntervalStatistics.Interval i : intervalList) {
totalDistance += i.getDistance_m();
totalTime += ((i.getDistance_m() / i.getSpeed_ms()) * UnitConversions.S_TO_MS);
}
// then
assertEquals(trackStatistics.getTotalDistance(), totalDistance, 0.01);
assertEquals(trackStatistics.getTotalTime() * UnitConversions.MS_TO_S, totalTime * UnitConversions.MS_TO_S, 0.1);
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance() / distanceInterval));
for (int i = 0; i < intervalList.size() - 1; i++) {
assertEquals(intervalList.get(i).getDistance_m(), distanceInterval, 0.001);
totalDistance -= intervalList.get(i).getDistance_m();
}
assertEquals(intervalList.get(intervalList.size() - 1).getDistance_m(), totalDistance, 0.01);
}
}
@@ -18,6 +18,10 @@ package de.dennisguse.opentracks;
import android.content.Intent;
import java.util.ArrayList;
import java.util.Arrays;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -55,17 +59,17 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
*
* @param trackIds the track ids
*/
protected void deleteTracks(long[] trackIds) {
protected void deleteTracks(Track.Id... trackIds) {
ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds);
}
@Override
public void onConfirmDeleteDone(long[] trackIds) {
public void onConfirmDeleteDone(Track.Id... trackIds) {
boolean stopRecording = false;
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
for (long trackId : trackIds) {
if (trackId == recordingTrackId) {
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
for (Track.Id trackId : trackIds) {
if (trackId.equals(recordingTrackId)) {
stopRecording = true;
break;
}
@@ -75,7 +79,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
getTrackRecordingServiceConnection().stopRecording(this, false);
}
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
intent.putExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, trackIds);
intent.putParcelableArrayListExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds)));
startActivityForResult(intent, DELETE_REQUEST_CODE);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
@@ -48,32 +48,33 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
private static final String TAG = MarkerDetailActivity.class.getSimpleName();
private List<Long> markerIds;
private List<Waypoint.Id> waypointIds;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
if (markerId == -1L) {
Waypoint.Id waypointId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
if (waypointId == null) {
Log.d(TAG, "invalid marker id");
finish();
return;
}
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
Waypoint waypoint = contentProviderUtils.getWaypoint(waypointId);
markerIds = new ArrayList<>();
waypointIds = new ArrayList<>();
int markerIndex = -1;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), -1L, -1)) {
//TODO Load only waypointIds, not the whole waypoint
try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
Waypoint currentMarker = contentProviderUtils.createWaypoint(cursor);
markerIds.add(currentMarker.getId());
if (currentMarker.getId() == markerId) {
markerIndex = markerIds.size() - 1;
waypointIds.add(currentMarker.getId());
if (waypointId.equals(currentMarker.getId())) {
markerIndex = waypointIds.size() - 1;
}
cursor.moveToNext();
@@ -126,7 +127,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
@Override
@NonNull
public Fragment getItem(int position) {
return MarkerDetailFragment.newInstance(markerIds.get(position));
return MarkerDetailFragment.newInstance(waypointIds.get(position));
}
@Nullable
@@ -137,7 +138,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
@Override
public int getCount() {
return markerIds.size();
return waypointIds.size();
}
}
}
@@ -42,6 +42,7 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -64,7 +65,7 @@ public class MarkerEditActivity extends AbstractActivity {
private static final int GALLERY_IMG_REQUEST_CODE = 7;
private static final String TAG = MarkerEditActivity.class.getSimpleName();
private long trackId;
private Track.Id trackId;
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
private Waypoint waypoint;
@@ -85,8 +86,8 @@ public class MarkerEditActivity extends AbstractActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
Waypoint.Id waypointId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
@@ -111,7 +112,7 @@ public class MarkerEditActivity extends AbstractActivity {
Button cancel = findViewById(R.id.marker_edit_cancel);
cancel.setOnClickListener(v -> finish());
final boolean isNewMarker = markerId == -1L;
final boolean isNewMarker = waypointId == null;
setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit);
Button done = findViewById(R.id.marker_edit_done);
@@ -126,7 +127,7 @@ public class MarkerEditActivity extends AbstractActivity {
});
if (isNewMarker) {
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
int nextWaypointNumber = trackId == null ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
if (nextWaypointNumber == -1) {
nextWaypointNumber = 0;
}
@@ -135,7 +136,7 @@ public class MarkerEditActivity extends AbstractActivity {
waypointMarkerType.setText("");
waypointDescription.setText("");
} else {
waypoint = new ContentProviderUtils(this).getWaypoint(markerId);
waypoint = new ContentProviderUtils(this).getWaypoint(waypointId);
if (waypoint == null) {
Log.d(TAG, "waypoint is null");
finish();
@@ -260,8 +261,8 @@ public class MarkerEditActivity extends AbstractActivity {
/**
* Returns the trackId. If trackId == -1 then get it from waypoint object.
*/
private long getTrackId() {
return trackId == -1 ? waypoint.getTrackId() : trackId;
private Track.Id getTrackId() {
return trackId == null ? waypoint.getTrackId() : trackId;
}
/**
@@ -32,10 +32,10 @@ import androidx.annotation.NonNull;
import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
@@ -57,16 +57,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private static final String TAG = MarkerListActivity.class.getSimpleName();
private static final String[] PROJECTION = new String[]{WaypointsColumns._ID,
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@@ -117,19 +112,19 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null;
track = trackId != null ? contentProviderUtils.getTrack(trackId) : null;
listView = findViewById(R.id.marker_list);
listView.setEmptyView(findViewById(R.id.marker_list_empty));
listView.setOnItemClickListener((parent, view, position, id) -> {
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id);
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, new Waypoint.Id(id));
startActivity(intent);
});
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@@ -163,14 +158,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
if (track != null) {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.TRACKID + "=?",
new String[]{String.valueOf(track.getId())}, null);
} else {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
null, null, null);
}
return ContentProviderUtils.getWaypointsLoader(MarkerListActivity.this, track != null ? track.getId() : null);
}
@Override
@@ -223,7 +211,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
insertMarkerMenuItem.setVisible(track != null && track.getId() == recordingTrackId && !recordingTrackPaused);
insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingTrackId) && !recordingTrackPaused);
return super.onPrepareOptionsMenu(menu);
}
@@ -241,30 +229,35 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
/**
* Handles a context item selection.
*
* @param itemId the menu item id
* @param markerIds the marker ids
* @param itemId the menu item id
* @param longWaypointIds the marker ids
* @return true if handled.
*/
private boolean handleContextItem(int itemId, long[] markerIds) {
private boolean handleContextItem(int itemId, long... longWaypointIds) {
Waypoint.Id[] waypointIds = new Waypoint.Id[longWaypointIds.length];
for (int i = 0; i < longWaypointIds.length; i++) {
waypointIds[i] = new Waypoint.Id(longWaypointIds[i]);
}
Intent intent;
switch (itemId) {
case R.id.list_context_menu_show_on_map:
if (markerIds.length == 1) {
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getWaypoint(markerIds[0]));
if (waypointIds.length == 1) {
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getWaypoint(waypointIds[0]));
}
return true;
case R.id.list_context_menu_edit:
if (markerIds.length == 1) {
if (waypointIds.length == 1) {
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]);
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, waypointIds[0]);
startActivity(intent);
}
return true;
case R.id.list_context_menu_delete:
if (markerIds.length > 1 && markerIds.length == listView.getCount()) {
markerIds = new long[]{-1L};
if (waypointIds.length > 1 && waypointIds.length == listView.getCount()) {
waypointIds = null;
}
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), waypointIds);
return true;
case R.id.list_context_menu_select_all:
for (int i = 0; i < listView.getCount(); i++) {
@@ -97,7 +97,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
private boolean metricUnits = true;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
@@ -203,7 +203,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
listView.setEmptyView(findViewById(R.id.search_list_empty));
listView.setOnItemClickListener((parent, view, position, id) -> {
Map<String, Object> item = arrayAdapter.getItem(position);
Long trackId = (Long) item.get(TRACK_ID_FIELD);
Track.Id trackId = (Track.Id) item.get(TRACK_ID_FIELD);
Long markerId = (Long) item.get(MARKER_ID_FIELD);
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class);
if (markerId != null) {
@@ -270,22 +270,22 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
return false;
}
Map<String, Object> item = arrayAdapter.getItem(positions[0]);
Long trackId = (Long) item.get(TRACK_ID_FIELD);
Long markerId = (Long) item.get(MARKER_ID_FIELD);
Track.Id trackId = (Track.Id) item.get(TRACK_ID_FIELD);
Waypoint.Id waypointId = (Waypoint.Id) item.get(MARKER_ID_FIELD);
Intent intent;
switch (itemId) {
case R.id.list_context_menu_show_on_map:
IntentUtils.showCoordinateOnMap(this, (double) item.get(MARKER_LATITUDE_FIELD), (double) item.get(MARKER_LONGITUDE_FIELD), item.get(NAME_FIELD) + "");
return true;
case R.id.list_context_menu_share:
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
intent = IntentUtils.newShareFileIntent(this, trackId);
intent = Intent.createChooser(intent, null);
startActivity(intent);
return true;
case R.id.list_context_menu_edit:
if (markerId != null) {
if (waypointId != null) {
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, waypointId);
} else {
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
@@ -296,10 +296,10 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
finish();
return true;
case R.id.list_context_menu_delete:
if (markerId != null) {
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), new long[]{markerId});
if (waypointId != null) {
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), waypointId);
} else {
deleteTracks(new long[]{trackId});
deleteTracks(trackId);
}
return true;
default:
@@ -331,7 +331,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
* @param textQuery the query
*/
private void doSearch(String textQuery) {
SearchQuery query = new SearchQuery(textQuery, null, -1L, System.currentTimeMillis());
SearchQuery query = new SearchQuery(textQuery, null, null, System.currentTimeMillis());
SortedSet<ScoredResult> scoredResults = searchEngine.search(query);
final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults);
@@ -372,8 +372,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
//TODO: It may be more appropriate to obtain the track name as a join in the retrieval phase of the searchable.
String trackName = null;
long trackId = waypoint.getTrackId();
if (trackId != -1L) {
Track.Id trackId = waypoint.getTrackId();
if (!trackId.isValid()) {
Track track = contentProviderUtils.getTrack(trackId);
if (track != null) {
trackName = track.getName();
@@ -8,7 +8,9 @@ import android.util.Pair;
import androidx.appcompat.app.AppCompatActivity;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ShareContentProvider;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
@@ -26,9 +28,9 @@ public abstract class ShowOnMapProxyActivity extends AppCompatActivity {
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long[] trackIds = IntentDashboardUtils.extractTrackIdsFromIntent(getIntent());
Set<Track.Id> trackIds = IntentDashboardUtils.extractTrackIdsFromIntent(getIntent());
showTrackTrackfileFormat(this, trackFileFormat, trackIds);
showTrackfileFormat(this, trackFileFormat, trackIds);
finish();
}
@@ -39,14 +41,14 @@ public abstract class ShowOnMapProxyActivity extends AppCompatActivity {
* @param context the context
* @param trackIds the track ids
*/
private static void showTrackTrackfileFormat(Context context, TrackFileFormat trackFileFormat, long[] trackIds) {
if (trackIds.length == 0) {
private static void showTrackfileFormat(Context context, TrackFileFormat trackFileFormat, Set<Track.Id> trackIds) {
if (trackIds.size() == 0) {
return;
}
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]);
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", trackFileFormat);
intent.setDataAndType(uriAndMime.first, uriAndMime.second);
@@ -32,6 +32,11 @@ public class Startup extends Application {
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
}
//TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed.
if (PreferencesUtils.isRecording(this)) {
Log.e(TAG, "Reset recordingTrackId; likely the TrackRecordingService crashed.");
PreferencesUtils.setLong(this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
}
//In debug builds: show thread and VM warnings.
if (BuildConfig.DEBUG) {
@@ -19,6 +19,7 @@ package de.dennisguse.opentracks;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -28,6 +29,8 @@ import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.util.ActivityUtils;
@@ -134,7 +137,7 @@ public class TrackController {
private final Activity activity;
private final OnClickListener clickListener;
private final int delayMillis;
private final AnimatedVectorDrawable animatedDrawable;
private final Drawable drawable;
private final int delayMessageId;
private ButtonDelay(final Activity activity, final ImageButton imageButton, final int animDrawableId, final int delayMessageId, final OnClickListener clickListener) {
@@ -142,24 +145,30 @@ public class TrackController {
this.clickListener = clickListener;
this.delayMillis = activity.getResources().getInteger(R.integer.buttonDelayMillis);
this.imageButton = imageButton;
this.animatedDrawable = (AnimatedVectorDrawable) activity.getDrawable(animDrawableId);
this.drawable = ContextCompat.getDrawable(activity, animDrawableId);
this.delayMessageId = delayMessageId;
}
@Override
public void run() {
imageButton.setImageDrawable(animatedDrawable);
activity.runOnUiThread(animatedDrawable::start);
activity.runOnUiThread(()->{
imageButton.setImageDrawable(drawable);
if (drawable instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) drawable).start();
}
ActivityUtils.vibrate(activity, 150);
ActivityUtils.toast(activity, delayMessageId, Toast.LENGTH_SHORT, Gravity.TOP);
});
ActivityUtils.vibrate(activity, 150);
activity.runOnUiThread(()-> ActivityUtils.toast(activity, delayMessageId, Toast.LENGTH_SHORT, Gravity.TOP));
try {
Thread.sleep(delayMillis);
} catch (InterruptedException ignored) {
}
if (!canceled) {
activity.runOnUiThread(()-> clickListener.onClick(null));
ActivityUtils.vibrate(activity, 1000);
activity.runOnUiThread(()-> {
clickListener.onClick(null);
ActivityUtils.vibrate(activity, 1000);
});
}
}
}
@@ -21,6 +21,9 @@ import android.os.Bundle;
import android.os.PowerManager;
import android.view.View;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.SystemUtils;
@@ -33,7 +36,7 @@ public class TrackDeleteActivity extends AbstractActivity {
public static final String EXTRA_TRACK_IDS = "track_ids";
private long[] trackIds;
private List<Track.Id> trackIds;
private Thread deleteThread;
@@ -44,13 +47,13 @@ public class TrackDeleteActivity extends AbstractActivity {
setResult(RESULT_CANCELED);
Intent intent = getIntent();
trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS);
trackIds = intent.getParcelableArrayListExtra(EXTRA_TRACK_IDS);
deleteThread = new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(TrackDeleteActivity.this);
PowerManager.WakeLock wakeLock = SystemUtils.acquireWakeLock(TrackDeleteActivity.this, null);
for (long id : trackIds) {
for (Track.Id id : trackIds) {
if (Thread.interrupted()) {
break;
}
@@ -65,8 +65,8 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
super.onCreate(bundle);
trackRecordingServiceConnection = new TrackRecordingServiceConnection();
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
if (trackId == -1L) {
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
if (!trackId.isValid()) {
Log.e(TAG, "invalid trackId");
finish();
return;
@@ -43,9 +43,9 @@ import androidx.core.app.ActivityCompat;
import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
@@ -60,7 +60,6 @@ import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
/**
* An activity displaying a list of tracks.
@@ -71,10 +70,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private static final String TAG = TrackListActivity.class.getSimpleName();
private static final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
// The following are set in onCreate
private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences;
@@ -87,8 +82,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private final LoaderCallbacks<Cursor> loaderCallbacks = new LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, null,
null, TrackUtils.TRACK_SORT_ORDER);
return ContentProviderUtils.getTracksCursorLoader(TrackListActivity.this);
}
@Override
@@ -104,7 +98,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Preferences
private boolean metricUnits = true;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private Track.Id recordingTrackId = null;
// Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@@ -125,8 +119,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private boolean recordingTrackPaused;
private final OnSharedPreferenceChangeListener
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
@@ -245,14 +238,14 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
listView.setEmptyView(findViewById(R.id.track_list_empty_view));
listView.setOnItemClickListener((parent, view, position, trackId) -> {
Intent newIntent;
if (trackId == recordingTrackId) {
if (trackId == recordingTrackId.getId()) {
// Is recording -> open record activity.
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
} else {
// Not recording -> open detail activity.
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
}
startActivity(newIntent);
});
@@ -269,7 +262,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION);
long trackId = cursor.getLong(idIndex);
Track.Id trackId = new Track.Id(cursor.getLong(idIndex));
boolean isRecording = trackId == recordingTrackId;
String icon = cursor.getString(iconIndex);
int iconId = TrackIconUtils.getIconDrawable(icon);
@@ -459,8 +452,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
/**
* Updates the menu items with the icon specified.
*
* @param isGpsStarted true if gps is started
* @param isRecording true if recording
* @param isGpsStarted true if gps is started
* @param isRecording true if recording
*/
private void updateGpsMenuItem(boolean isGpsStarted, boolean isRecording) {
if (startGpsMenuItem != null) {
@@ -478,15 +471,20 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
/**
* Handles a context item selection.
*
* @param itemId the menu item id
* @param trackIds the track ids
* @param itemId the menu item id
* @param longTrackIds the track ids
* @return true if handled.
*/
private boolean handleContextItem(int itemId, long[] trackIds) {
private boolean handleContextItem(int itemId, long... longTrackIds) {
Track.Id[] trackIds = new Track.Id[longTrackIds.length];
for (int i = 0; i < longTrackIds.length; i++) {
trackIds[i] = new Track.Id(longTrackIds[i]);
}
Intent intent;
switch (itemId) {
case R.id.list_context_menu_show_on_map:
IntentDashboardUtils.startDashboard(this, trackIds, false);
IntentDashboardUtils.startDashboard(this, false, trackIds);
return true;
case R.id.list_context_menu_share:
intent = IntentUtils.newShareFileIntent(this, trackIds);
@@ -24,6 +24,7 @@ import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
@@ -36,6 +37,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ChartFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.IntervalsRecordedFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordedFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.SettingsActivity;
@@ -65,8 +67,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
private TrackDataHub trackDataHub;
private ViewPager pager;
// From intent.
private long trackId;
private Track track;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -77,42 +78,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
trackDataHub = new TrackDataHub(this);
FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), 1) {
@Override
public int getCount() {
return 3;
}
@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new StatisticsRecordedFragment();
case 1:
return ChartFragment.newInstance(false);
case 2:
return ChartFragment.newInstance(true);
default:
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.track_detail_stats_tab);
case 1:
return getString(R.string.settings_chart_by_time);
case 2:
return getString(R.string.settings_chart_by_distance);
}
return "Unknown Tab";
}
};
pager = findViewById(R.id.track_detail_activity_view_pager);
pager.setAdapter(adapter);
pager.setAdapter(new CustomFragmentPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT));
TabLayout tabs = findViewById(R.id.track_detail_activity_tablayout);
tabs.setupWithViewPager(pager);
if (savedInstanceState != null) {
@@ -133,8 +100,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
// Update UI
this.invalidateOptionsMenu();
if (trackId != -1L) {
trackDataHub.loadTrack(trackId);
if (track != null) {
trackDataHub.loadTrack(track.getId());
}
}
@@ -172,7 +139,6 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(this));
Track track = contentProviderUtils.getTrack(trackId);
setTitle(track != null ? track.getName() : "");
return super.onPrepareOptionsMenu(menu);
}
@@ -182,29 +148,29 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
Intent intent;
switch (item.getItemId()) {
case R.id.track_detail_share:
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId});
intent = IntentUtils.newShareFileIntent(this, track.getId());
intent = Intent.createChooser(intent, null);
startActivity(intent);
return true;
case R.id.track_detail_menu_show_on_map:
IntentDashboardUtils.startDashboard(this, new long[]{trackId}, false);
IntentDashboardUtils.startDashboard(this, false, track.getId());
return true;
case R.id.track_detail_markers:
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, track.getId());
startActivity(intent);
return true;
case R.id.track_detail_edit:
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, track.getId());
startActivity(intent);
return true;
case R.id.track_detail_delete:
deleteTracks(new long[]{trackId});
deleteTracks(track.getId());
return true;
case R.id.track_detail_resume_track:
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, track.getId());
startActivity(newIntent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish();
@@ -238,12 +204,12 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
}
private void handleIntent(Intent intent) {
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
Track.Id trackId = intent.getParcelableExtra(EXTRA_TRACK_ID);
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L);
if (markerId != -1L) {
Waypoint.Id waypointId = intent.getParcelableExtra(EXTRA_MARKER_ID);
if (waypointId != null) {
// Use the trackId from the marker
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
Waypoint waypoint = contentProviderUtils.getWaypoint(waypointId);
if (waypoint == null) {
finish();
return;
@@ -251,10 +217,12 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
trackId = waypoint.getTrackId();
}
if (trackId == -1L) {
if (trackId == null) {
Log.e(TAG, "TrackDetailActivity needs EXTRA_TRACK_ID.");
finish();
}
track = contentProviderUtils.getTrack(trackId);
}
public void chooseActivityType(String category) {
@@ -263,8 +231,52 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
@Override
public void onChooseActivityTypeDone(String iconValue) {
Track track = contentProviderUtils.getTrack(trackId);
String category = getString(TrackIconUtils.getIconActivityType(iconValue));
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
}
private class CustomFragmentPagerAdapter extends FragmentPagerAdapter {
public CustomFragmentPagerAdapter(@NonNull FragmentManager fm, int behavior) {
super(fm, behavior);
}
@Override
public int getCount() {
return 4;
}
@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return StatisticsRecordedFragment.newInstance(track.getId());
case 1:
return IntervalsRecordedFragment.newInstance(track.getId());
case 2:
return ChartFragment.newInstance(false);
case 3:
return ChartFragment.newInstance(true);
default:
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.track_detail_stats_tab);
case 1:
return getString(R.string.track_detail_intervals_tab);
case 2:
return getString(R.string.settings_chart_by_time);
case 3:
return getString(R.string.settings_chart_by_distance);
default:
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
}
}
}
}
@@ -25,6 +25,7 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ChartFragment;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.IntervalsRecordingFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
@@ -60,7 +61,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private TrackController trackController;
// Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
private long trackId;
private Track.Id trackId;
// Preferences
private boolean recordingTrackPaused;
@@ -78,7 +79,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
if (!service.isRecording()) {
// Starts or resumes a track.
if (trackId == -1L) {
if (trackId == null) {
// trackId isn't initialized -> leads a new recording.
trackId = service.startNewTrack();
} else {
@@ -161,13 +162,15 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
super.onCreate(savedInstanceState);
contentProviderUtils = new ContentProviderUtils(this);
trackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
trackId = null;
if (savedInstanceState != null) {
//Activity was recreated.
trackId = savedInstanceState.getLong(EXTRA_TRACK_ID, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
trackId = savedInstanceState.getParcelable(EXTRA_TRACK_ID);
} else {
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT && contentProviderUtils.getTrack(trackId) == null) {
// Resume a track
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
if (trackId != null && contentProviderUtils.getTrack(trackId) == null) {
Log.w(TAG, "TrackId does not exists; cannot continue the recording.");
finish();
}
}
@@ -179,7 +182,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
trackDataHub = new TrackDataHub(this);
pager = findViewById(R.id.track_detail_activity_view_pager);
pager.setAdapter(new CustomFragmentPagerAdapter(getSupportFragmentManager(), 1));
pager.setAdapter(new CustomFragmentPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT));
TabLayout tabs = findViewById(R.id.track_detail_activity_tablayout);
tabs.setupWithViewPager(pager);
if (savedInstanceState != null) {
@@ -247,7 +250,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
// Update UI
this.invalidateOptionsMenu();
if (trackId != -1L) {
if (trackId != null) {
trackDataHub.loadTrack(trackId);
trackController.onResume(true, recordingTrackPaused);
}
@@ -265,7 +268,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
outState.putLong(EXTRA_TRACK_ID, trackId);
outState.putParcelable(EXTRA_TRACK_ID, trackId);
}
@Override
@@ -314,7 +317,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
startActivity(intent);
return true;
case R.id.track_detail_menu_show_on_map:
IntentDashboardUtils.startDashboard(this, new long[]{trackId}, true);
IntentDashboardUtils.startDashboard(this, true, trackId);
return true;
case R.id.track_detail_markers:
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
@@ -371,7 +374,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@Override
public int getCount() {
return 3;
return 4;
}
@NonNull
@@ -379,10 +382,12 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
public Fragment getItem(int position) {
switch (position) {
case 0:
return new StatisticsRecordingFragment();
return StatisticsRecordingFragment.newInstance();
case 1:
return ChartFragment.newInstance(false);
return IntervalsRecordingFragment.newInstance(trackId);
case 2:
return ChartFragment.newInstance(false);
case 3:
return ChartFragment.newInstance(true);
default:
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
@@ -395,8 +400,10 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
case 0:
return getString(R.string.track_detail_stats_tab);
case 1:
return getString(R.string.settings_chart_by_time);
return getString(R.string.track_detail_intervals_tab);
case 2:
return getString(R.string.settings_chart_by_time);
case 3:
return getString(R.string.settings_chart_by_distance);
default:
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
@@ -0,0 +1,68 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.Interval> {
private boolean metricUnits;
private float sumDistance_m = 0f;
public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList) {
super(context, R.layout.interval_stats_list_item, intervalList);
metricUnits = PreferencesUtils.isMetricUnits(context);
}
@NonNull
@Override
public View getView(int position, @Nullable View intervalView, @NonNull ViewGroup parent) {
IntervalStatistics.Interval interval = getItem(position);
ViewHolder viewHolder;
if (intervalView == null) {
viewHolder = new ViewHolder();
intervalView = LayoutInflater.from(getContext()).inflate(R.layout.interval_stats_list_item, parent, false);
viewHolder.distance = intervalView.findViewById(R.id.interval_item_distance);
viewHolder.speed = intervalView.findViewById(R.id.interval_item_speed);
viewHolder.pace = intervalView.findViewById(R.id.interval_item_pace);
intervalView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) intervalView.getTag();
}
sumDistance_m += interval.getDistance_m();
viewHolder.distance.setText(StringUtils.formatDistance(getContext(), sumDistance_m, metricUnits));
Pair<String, String> speedParts = StringUtils.getSpeedParts(getContext(), interval.getSpeed_ms(), metricUnits, true);
viewHolder.speed.setText(speedParts.first + " " + speedParts.second);
Pair<String, String> paceParts = StringUtils.getSpeedParts(getContext(), interval.getSpeed_ms(), metricUnits, false);
viewHolder.pace.setText(paceParts.first + " " + paceParts.second);
return intervalView;
}
private static class ViewHolder {
private TextView distance;
private TextView speed;
private TextView pace;
}
}
@@ -123,8 +123,8 @@ public class SearchEngine {
}
// Finally, use arbitrary ordering, by ID.
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId();
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId();
long id1 = r1.track != null ? r1.track.getId().getId() : r1.waypoint.getId().getId();
long id2 = r2.track != null ? r2.track.getId().getId() : r2.waypoint.getId().getId();
long idDiff = id2 - id1;
return Long.signum(idDiff);
};
@@ -290,7 +290,7 @@ public class SearchEngine {
score *= getTimeBoost(query, location.getTime());
// Score waypoints in the currently-selected track higher (searching inside the current track).
if (query.currentTrackId != -1 && waypoint.getTrackId() == query.currentTrackId) {
if (waypoint.getTrackId() != null && waypoint.getTrackId().equals(query.currentTrackId)) {
score *= CURRENT_TRACK_WAYPOINT_PROMOTION;
}
@@ -382,10 +382,10 @@ public class SearchEngine {
public static class SearchQuery {
final String textQuery;
final Location currentLocation;
final long currentTrackId;
final Track.Id currentTrackId;
final long currentTimestamp;
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId, long currentTimestamp) {
public SearchQuery(String textQuery, Location currentLocation, Track.Id currentTrackId, long currentTimestamp) {
this.textQuery = textQuery.toLowerCase(Locale.getDefault());
this.currentLocation = currentLocation;
this.currentTrackId = currentTrackId;
@@ -417,8 +417,8 @@ public class SearchEngine {
@Override
public String toString() {
return "ScoredResult ["
+ (track != null ? ("trackId=" + track.getId() + ", ") : "")
+ (waypoint != null ? ("wptId=" + waypoint.getId() + ", ") : "")
+ (track != null ? ("trackId=" + track.getId().getId() + ", ") : "")
+ (waypoint != null ? ("wptId=" + waypoint.getId().getId() + ", ") : "")
+ "score=" + score + "]";
}
}
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.Collections;
@@ -78,8 +79,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
private Handler handler;
// Preference values
private long selectedTrackId;
private long recordingTrackId;
private Track.Id selectedTrackId;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
// Track points sampling state
@@ -174,10 +175,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
handler = null;
}
public void loadTrack(final long trackId) {
public void loadTrack(final @NonNull Track.Id trackId) {
runInHandlerThread(() -> {
if (trackId == selectedTrackId) {
Log.i(TAG, "Not reloading track " + trackId);
if (trackId.equals(selectedTrackId)) {
Log.i(TAG, "Not reloading track " + trackId.getId());
return;
}
selectedTrackId = trackId;
@@ -212,14 +213,14 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
* Returns true if the selected track is recording.
*/
public boolean isSelectedTrackRecording() {
return selectedTrackId == recordingTrackId && PreferencesUtils.isRecording(recordingTrackId);
return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && PreferencesUtils.isRecording(recordingTrackId);
}
/**
* Returns true if the selected track is paused.
*/
public boolean isSelectedTrackPaused() {
return selectedTrackId == recordingTrackId && recordingTrackPaused;
return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && recordingTrackPaused;
}
@Override
@@ -311,7 +312,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
trackDataListener.clearWaypoints();
}
try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, null, MAX_DISPLAYED_WAYPOINTS)) {
if (cursor != null && cursor.moveToFirst()) {
do {
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
@@ -356,6 +357,11 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L;
long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId;
if (selectedTrackId == null) {
Log.w(TAG, "This should not happen, but it does"); //TODO
return;
}
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1;
boolean includeNextPoint = false;
@@ -16,9 +16,17 @@
package de.dennisguse.opentracks.content.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
import java.util.UUID;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* A track.
@@ -28,7 +36,7 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
*/
public class Track {
private long id = -1L;
private Track.Id id;
private UUID uuid = UUID.randomUUID();
private String name = "";
@@ -42,11 +50,15 @@ public class Track {
public Track() {
}
public long getId() {
/**
* May be null if the track was not loaded from the database.
*/
public @Nullable
Id getId() {
return id;
}
public void setId(long id) {
public void setId(Id id) {
this.id = id;
}
@@ -97,4 +109,66 @@ public class Track {
public void setTrackStatistics(TrackStatistics trackStatistics) {
this.trackStatistics = trackStatistics;
}
public static class Id implements Parcelable {
private final long id;
public Id(long id) {
this.id = id;
}
public Id(@NonNull String id) {
this(Long.parseLong(id));
}
//TOOD Limit visibility to TrackRecordingService / ContentProvider
public long getId() {
return id;
}
@Deprecated //TODO Use a Track.Id of null instead
public boolean isValid() {
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Id id1 = (Id) o;
return id == id1.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public int describeContents() {
return 0;
}
@NonNull
@Override
public String toString() {
throw new RuntimeException("Not supported");
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(id);
}
public static final Parcelable.Creator<Track.Id> CREATOR = new Parcelable.Creator<Track.Id>() {
public Track.Id createFromParcel(Parcel in) {
return new Track.Id(in.readLong());
}
public Track.Id[] newArray(int size) {
return new Track.Id[size];
}
};
}
}
@@ -18,10 +18,17 @@ package de.dennisguse.opentracks.content.data;
import android.location.Location;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.Objects;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* A waypoint.
*
@@ -30,12 +37,12 @@ import androidx.annotation.VisibleForTesting;
*/
public final class Waypoint {
private long id = -1L;
private Id id;
private String name = "";
private String description = "";
private String category = "";
private String icon = "";
private long trackId = -1L;
private Track.Id trackId;
private double length = 0.0;
private long duration = 0;
private Location location;
@@ -51,7 +58,7 @@ public final class Waypoint {
this.location = location;
}
public Waypoint(String name, String description, String category, String icon, long trackId, double length, long duration, @NonNull Location location, String photoUrl) {
public Waypoint(String name, String description, String category, String icon, @NonNull Track.Id trackId, double length, long duration, @NonNull Location location, String photoUrl) {
this.name = name;
this.description = description;
this.category = category;
@@ -63,11 +70,15 @@ public final class Waypoint {
this.photoUrl = photoUrl;
}
public long getId() {
/**
* May be null if the waypoint was not loaded from the database.
*/
public @Nullable
Id getId() {
return id;
}
public void setId(long id) {
public void setId(Id id) {
this.id = id;
}
@@ -103,11 +114,11 @@ public final class Waypoint {
this.icon = icon;
}
public long getTrackId() {
public Track.Id getTrackId() {
return trackId;
}
public void setTrackId(long trackId) {
public void setTrackId(Track.Id trackId) {
this.trackId = trackId;
}
@@ -147,4 +158,66 @@ public final class Waypoint {
public boolean hasPhoto() {
return photoUrl != null && !"".equals(photoUrl);
}
public static class Id implements Parcelable {
private final long id;
public Id(long id) {
this.id = id;
}
public Id(@NonNull String id) {
this(Long.parseLong(id));
}
//TOOD Limit visibility to TrackRecordingService / ContentProvider
public long getId() {
return id;
}
@Deprecated //TODO Use a Waypoint.Id of null instead
public boolean isValid() {
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Id id1 = (Id) o;
return id == id1.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@NonNull
@Override
public String toString() {
throw new RuntimeException("Not supported");
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(id);
}
public static final Parcelable.Creator<Id> CREATOR = new Parcelable.Creator<Id>() {
public Id createFromParcel(Parcel in) {
return new Id(in.readLong());
}
public Id[] newArray(int size) {
return new Id[size];
}
};
}
}
@@ -23,9 +23,13 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import java.io.File;
import java.util.ArrayList;
@@ -44,6 +48,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.TrackUtils;
import de.dennisguse.opentracks.util.UUIDUtils;
/**
@@ -89,7 +94,7 @@ public class ContentProviderUtils {
*
* @param trackId the track id
*/
public void clearTrack(long trackId) {
public void clearTrack(Track.Id trackId) {
deleteTrackPointsAndWaypoints(trackId);
Track track = new Track();
track.setId(trackId);
@@ -121,7 +126,7 @@ public class ContentProviderUtils {
Track track = new Track();
TrackStatistics trackStatistics = track.getTrackStatistics();
if (!cursor.isNull(idIndex)) {
track.setId(cursor.getLong(idIndex));
track.setId(new Track.Id(cursor.getLong(idIndex)));
}
if (!cursor.isNull(uuidIndex)) {
track.setUuid(UUIDUtils.fromBytes(cursor.getBlob(uuidIndex)));
@@ -184,14 +189,17 @@ public class ContentProviderUtils {
*
* @param trackId the track id
*/
public void deleteTrack(Context context, long trackId) {
public void deleteTrack(Context context, Track.Id trackId) {
if (trackId == null) {
return;
}
deleteTrackPointsAndWaypoints(trackId);
// Delete track folder resources.
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
// Delete track last since it triggers a database vacuum call
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId)});
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())});
}
/**
@@ -199,15 +207,15 @@ public class ContentProviderUtils {
*
* @param trackId the track id
*/
private void deleteTrackPointsAndWaypoints(long trackId) {
String[] selectionArgs = new String[]{Long.toString(trackId)};
private void deleteTrackPointsAndWaypoints(@NonNull Track.Id trackId) {
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, TrackPointsColumns.TRACKID + "=?", selectionArgs);
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", selectionArgs);
}
@VisibleForTesting
public List<Track> getAllTracks() {
public List<Track> getTracks() {
ArrayList<Track> tracks = new ArrayList<>();
try (Cursor cursor = getTrackCursor(null, null, TracksColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
@@ -220,6 +228,17 @@ public class ContentProviderUtils {
return tracks;
}
public static Loader<Cursor> getTracksCursorLoader(Context context) {
String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
return new CursorLoader(context, TracksColumns.CONTENT_URI, PROJECTION, null, null, TrackUtils.TRACK_SORT_ORDER);
}
@Deprecated
//TODO Do not use; rather get the proper TrackId (e.g., resume a track might give you some weird effects).
@VisibleForTesting
public Track getLastTrack() {
try (Cursor cursor = getTrackCursor(null, null, TracksColumns.STARTTIME + " DESC")) {
// Using the same order as shown in the track list
@@ -233,11 +252,11 @@ public class ContentProviderUtils {
/**
* @param trackId the track id.
*/
public Track getTrack(long trackId) {
if (trackId < 0) {
public Track getTrack(Track.Id trackId) {
if (trackId == null || !trackId.isValid()) {
return null;
}
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId)}, null)) {
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor);
}
@@ -288,7 +307,7 @@ public class ContentProviderUtils {
* @param track the track
*/
public void updateTrack(Track track) {
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId())});
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId().getId())});
}
private ContentValues createContentValues(Track track) {
@@ -296,8 +315,8 @@ public class ContentProviderUtils {
TrackStatistics trackStatistics = track.getTrackStatistics();
// Value < 0 indicates no id is available
if (track.getId() >= 0) {
values.put(TracksColumns._ID, track.getId());
if (track.getId() != null && track.getId().isValid()) {
values.put(TracksColumns._ID, track.getId().getId());
}
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
values.put(TracksColumns.NAME, track.getName());
@@ -363,7 +382,7 @@ public class ContentProviderUtils {
Waypoint waypoint = new Waypoint(location);
if (!cursor.isNull(idIndex)) {
waypoint.setId(cursor.getLong(idIndex));
waypoint.setId(new Waypoint.Id(cursor.getLong(idIndex)));
}
if (!cursor.isNull(nameIndex)) {
waypoint.setName(cursor.getString(nameIndex));
@@ -378,7 +397,7 @@ public class ContentProviderUtils {
waypoint.setIcon(cursor.getString(iconIndex));
}
if (!cursor.isNull(trackIdIndex)) {
waypoint.setTrackId(cursor.getLong(trackIdIndex));
waypoint.setTrackId(new Track.Id(cursor.getLong(trackIdIndex)));
}
if (!cursor.isNull(lengthIndex)) {
waypoint.setLength(cursor.getFloat(lengthIndex));
@@ -393,10 +412,10 @@ public class ContentProviderUtils {
return waypoint;
}
public void deleteWaypoint(Context context, long waypointId) {
public void deleteWaypoint(Context context, Waypoint.Id waypointId) {
final Waypoint waypoint = getWaypoint(waypointId);
deleteWaypointPhoto(context, waypoint);
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)});
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId.getId())});
}
/**
@@ -405,13 +424,13 @@ public class ContentProviderUtils {
* @param trackId the track id
* @return -1 if not able to get the next waypoint number.
*/
public int getNextWaypointNumber(long trackId) {
if (trackId < 0) {
public int getNextWaypointNumber(Track.Id trackId) {
if (!trackId.isValid()) {
return -1;
}
String[] projection = {WaypointsColumns._ID};
String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1)) {
if (cursor != null) {
return cursor.getCount();
@@ -420,12 +439,12 @@ public class ContentProviderUtils {
return -1;
}
public Waypoint getWaypoint(long waypointId) {
if (waypointId < 0) {
public Waypoint getWaypoint(Waypoint.Id waypointId) {
if (!waypointId.isValid()) {
return null;
}
try (Cursor cursor = getWaypointCursor(null, WaypointsColumns._ID + "=?",
new String[]{Long.toString(waypointId)}, WaypointsColumns._ID, 1)) {
new String[]{Long.toString(waypointId.getId())}, WaypointsColumns._ID, 1)) {
if (cursor != null && cursor.moveToFirst()) {
return createWaypoint(cursor);
}
@@ -451,30 +470,30 @@ public class ContentProviderUtils {
* The caller owns the returned cursor and is responsible for closing it.
*
* @param trackId the track id
* @param minWaypointId the minimum waypoint id. -1L to ignore
* @param minWaypointId the minimum waypoint id. null to ignore
* @param maxWaypoints the maximum number of waypoints to return. -1 for no limit
*/
public Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints) {
if (trackId < 0) {
public Cursor getWaypointCursor(Track.Id trackId, @Nullable Waypoint.Id minWaypointId, int maxWaypoints) {
if (!trackId.isValid()) {
return null;
}
String selection;
String[] selectionArgs;
if (minWaypointId >= 0) {
if (minWaypointId != null && minWaypointId.isValid()) {
selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId), Long.toString(minWaypointId)};
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minWaypointId.getId())};
} else {
selection = WaypointsColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId)};
selectionArgs = new String[]{Long.toString(trackId.getId())};
}
return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints);
}
@VisibleForTesting
public List<Waypoint> getWaypoints(long trackId) {
public List<Waypoint> getWaypoints(Track.Id trackId) {
ArrayList<Waypoint> waypoints = new ArrayList<>();
try (Cursor cursor = getWaypointCursor(trackId, -1L, -1)) {
try (Cursor cursor = getWaypointCursor(trackId, null, -1)) {
if (cursor.moveToFirst()) {
do {
waypoints.add(createWaypoint(cursor));
@@ -484,19 +503,34 @@ public class ContentProviderUtils {
return waypoints;
}
public static Loader<Cursor> getWaypointsLoader(Context context, @Nullable Track.Id trackId) {
final String[] PROJECTION = new String[]{WaypointsColumns._ID,
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
if (trackId != null) {
return new CursorLoader(context, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.TRACKID + "=?",
new String[]{String.valueOf(trackId.getId())}, null);
} else {
return new CursorLoader(context, WaypointsColumns.CONTENT_URI, PROJECTION, null, null, null);
}
}
/**
* Gets the number of waypoints for a track.
*
* @param trackId the track id
*/
public int getWaypointCount(long trackId) {
if (trackId < 0) {
public int getWaypointCount(Track.Id trackId) {
if (!trackId.isValid()) {
return 0;
}
String[] projection = new String[]{"count(*) AS count"};
String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID)) {
if (cursor == null) {
return 0;
@@ -512,8 +546,8 @@ public class ContentProviderUtils {
* @param waypoint the waypoint
* @return the content provider URI of the inserted waypoint.
*/
public Uri insertWaypoint(Waypoint waypoint) {
waypoint.setId(-1L);
public Uri insertWaypoint(@NonNull Waypoint waypoint) {
waypoint.setId(null);
return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint));
}
@@ -548,22 +582,23 @@ public class ContentProviderUtils {
if (!updatedWaypoint.hasPhoto()) {
deleteWaypointPhoto(context, savedWaypoint);
}
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(updatedWaypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(updatedWaypoint.getId())});
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(updatedWaypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(updatedWaypoint.getId().getId())});
return rows == 1;
}
ContentValues createContentValues(Waypoint waypoint) {
ContentValues createContentValues(@NonNull Waypoint waypoint) {
ContentValues values = new ContentValues();
// Value < 0 indicates no id is available
if (waypoint.getId() >= 0) {
values.put(WaypointsColumns._ID, waypoint.getId());
if (waypoint.getId() != null && waypoint.getId().isValid()) {
values.put(WaypointsColumns._ID, waypoint.getId().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());
if (waypoint.getTrackId() != null) {
values.put(WaypointsColumns.TRACKID, waypoint.getTrackId().getId());
}
values.put(WaypointsColumns.LENGTH, waypoint.getLength());
values.put(WaypointsColumns.DURATION, waypoint.getDuration());
@@ -656,7 +691,7 @@ public class ContentProviderUtils {
* @return the number of trackPoints inserted
*/
//TODO Only used for testing and file import; might be better to replace it.
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, long trackId) {
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, Track.Id trackId) {
ContentValues[] values = new ContentValues[trackPoints.length];
for (int i = 0; i < values.length; i++) {
values[i] = createContentValues(trackPoints[i], trackId);
@@ -666,23 +701,23 @@ public class ContentProviderUtils {
/**
* Gets the first location id for a track.
* Returns -1L if it doesn't exist.
* Returns null if it doesn't exist.
*
* @param trackId the track id
*/
@Deprecated
public long getFirstTrackPointId(long trackId) {
if (trackId < 0) {
return -1L;
public Track.Id getFirstTrackPointId(Track.Id trackId) {
if (!trackId.isValid()) {
return null;
}
String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
return new Track.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
}
}
return -1L;
return null;
}
/**
@@ -692,12 +727,13 @@ public class ContentProviderUtils {
* @param trackId the track id
*/
@Deprecated
public long getLastTrackPointId(long trackId) {
if (trackId < 0) {
public long getLastTrackPointId(Track.Id trackId) {
if (trackId == null || !trackId.isValid()) {
Log.w(TAG, "Fix callers who do this.");
return -1L;
}
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
@@ -713,12 +749,12 @@ public class ContentProviderUtils {
* @param location the location
* @return trackPoint id if the location is in the track. -1L otherwise.
*/
public long getTrackPointId(long trackId, Location location) {
if (trackId < 0) {
public long getTrackPointId(Track.Id trackId, Location location) {
if (!trackId.isValid()) {
return -1L;
}
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())};
String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
@@ -744,8 +780,8 @@ public class 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)
*/
public Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending) {
if (trackId < 0) {
public Cursor getTrackPointCursor(Track.Id trackId, long startTrackPointId, int maxLocations, boolean descending) {
if (!trackId.isValid()) {
return null;
}
@@ -754,10 +790,10 @@ public class ContentProviderUtils {
if (startTrackPointId >= 0) {
String comparison = descending ? "<=" : ">=";
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison + "?";
selectionArgs = new String[]{Long.toString(trackId), Long.toString(startTrackPointId)};
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId)};
} else {
selection = TrackPointsColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId)};
selectionArgs = new String[]{Long.toString(trackId.getId())};
}
String sortOrder = TrackPointsColumns._ID;
@@ -777,12 +813,12 @@ public class ContentProviderUtils {
* @param trackId the track id
*/
@Deprecated
public TrackPoint getLastValidTrackPoint(long trackId) {
if (trackId < 0) {
public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
if (!trackId.isValid()) {
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)};
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
return findTrackPointBy(selection, selectionArgs);
}
@@ -793,19 +829,19 @@ public class ContentProviderUtils {
* @param trackId the track id
* @return the content provider URI of the inserted trackPoint
*/
public Uri insertTrackPoint(TrackPoint trackPoint, long trackId) {
public Uri insertTrackPoint(TrackPoint trackPoint, Track.Id trackId) {
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(trackPoint, trackId));
}
/**
* Creates the {@link ContentValues} for a {@link TrackPoint}.
*
* @param trackPoint the trackPoint
* @param trackPoint the trackPointstats_pace_km#87
* @param trackId the track id
*/
private ContentValues createContentValues(TrackPoint trackPoint, long trackId) {
private ContentValues createContentValues(TrackPoint trackPoint, Track.Id trackId) {
ContentValues values = new ContentValues();
values.put(TrackPointsColumns.TRACKID, trackId);
values.put(TrackPointsColumns.TRACKID, trackId.getId());
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
@@ -846,7 +882,7 @@ public class ContentProviderUtils {
* @param startTrackPointId the starting trackPoint id. -1L to ignore
* @param descending true to sort the result in descending order (latest location first)
*/
public TrackPointIterator getTrackPointLocationIterator(final long trackId, final long startTrackPointId, final boolean descending) {
public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final long startTrackPointId, final boolean descending) {
return new TrackPointIterator(this, trackId, startTrackPointId, descending);
}
@@ -872,7 +908,7 @@ public class ContentProviderUtils {
}
@VisibleForTesting
public List<TrackPoint> getTrackPoints(long trackId) {
public List<TrackPoint> getTrackPoints(Track.Id trackId) {
List<TrackPoint> trackPoints = null;
try (Cursor trackPointCursor = getTrackPointCursor(trackId, -1L, -1, false)) {
@@ -903,13 +939,23 @@ public class ContentProviderUtils {
this.defaultCursorBatchSize = defaultCursorBatchSize;
}
public static String formatIdListForUri(Track.Id... trackIds) {
long[] ids = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
ids[i] = trackIds[i].getId();
}
return formatIdListForUri(ids);
}
/**
* Formats an array of IDs as comma separated string value
*
* @param ids array with IDs
* @return comma separated list of ids
*/
public static String formatIdListForUri(long[] ids) {
private static String formatIdListForUri(long[] ids) {
StringBuilder idsPathSegment = new StringBuilder();
for (long id : ids) {
if (idsPathSegment.length() > 0) {
@@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting;
import java.util.UUID;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
@@ -162,7 +163,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
if (cursor.moveToFirst()) {
int trackIdIndex = cursor.getColumnIndexOrThrow("_id");
do {
long trackId = cursor.getLong(trackIdIndex);
Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex));
ContentValues cv = new ContentValues();
cv.put("uuid", UUIDUtils.toBytes(UUID.randomUUID()));
db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId)});
@@ -17,8 +17,11 @@ import androidx.annotation.Nullable;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import de.dennisguse.opentracks.android.IContentResolver;
import de.dennisguse.opentracks.content.data.Track;
@@ -29,9 +32,9 @@ import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
/**
* A content provider that mimics the behavior of {@link androidx.core.content.FileProvider}, which shares virtual (non-existing) KML-files.
* The actual content of the virtual files is generated by using the functionality defined in {@link CustomContentProvider}.
*
* <p>
* Moreover, it manages access to OpenTrack's database via {@link CustomContentProvider}.
*
* <p>
* Explanation:
* Although a request is handled by a {@link android.content.ContentProvider} (with temporarily granted permission), Android's security infrastructure prevents forwarding queries to non-exported {@link android.content.ContentProvider}.
* Thus, if {@link ShareContentProvider} and {@link CustomContentProvider} would be two different instances, the data would not be accessible to external apps.
@@ -70,14 +73,20 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
}
public static Pair<Uri, String> createURI(long[] trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
if (trackIds.length == 0) {
public static Pair<Uri, String> createURI(Track.Id trackId, String trackName, @NonNull TrackFileFormat trackFileFormat) {
Set<Track.Id> trackIds = new HashSet<>(1);
trackIds.add(trackId);
return createURI(trackIds, trackName, trackFileFormat);
}
public static Pair<Uri, String> createURI(Set<Track.Id> trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
if (trackIds.size() == 0) {
throw new UnsupportedOperationException();
}
StringBuilder trackIdBuilder = new StringBuilder();
for (long trackId : trackIds) {
trackIdBuilder.append(trackId).append(TRACKID_DELIMITER);
for (Track.Id trackId : trackIds) {
trackIdBuilder.append(trackId.getId()).append(TRACKID_DELIMITER);
}
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
@@ -89,18 +98,18 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
return new Pair<>(uri, mime);
}
static long[] parseURI(Uri uri) {
static Set<Track.Id> parseURI(Uri uri) {
List<String> uriPaths = uri.getPathSegments();
if (uriPaths == null || uriPaths.size() < 3) {
Log.d(TAG, "URI does not contain any trackIds.");
return new long[]{};
return new HashSet<>();
}
String[] uriTrackIds = uriPaths.get(2).split(TRACKID_DELIMITER);
long[] trackIds = new long[uriTrackIds.length];
for (int i = 0; i < trackIds.length; i++) {
trackIds[i] = Long.parseLong(uriTrackIds[i]);
Set<Track.Id> trackIds = new HashSet<>();
for (String uriTrackId : uriTrackIds) {
trackIds.add(new Track.Id(Long.parseLong(uriTrackId)));
}
return trackIds;
}
@@ -202,17 +211,17 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
long[] trackIds = parseURI(uri);
final Track[] tracks = new Track[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
tracks[i] = contentProviderUtils.getTrack(trackIds[i]);
Set<Track.Id> trackIds = parseURI(uri);
final ArrayList<Track> tracks = new ArrayList<>();
for (Track.Id trackId : trackIds) {
tracks.add(contentProviderUtils.getTrack(trackId));
}
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext());
PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> {
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
trackExporter.writeTrack(tracks, fileOutputStream);
trackExporter.writeTrack(tracks.toArray(new Track[0]), fileOutputStream);
} catch (IOException e) {
Log.w(TAG, "there occurred an error while sharing a file: " + e);
}
@@ -6,6 +6,7 @@ import android.util.Log;
import java.util.Iterator;
import java.util.NoSuchElementException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
/**
@@ -16,14 +17,14 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private static final String TAG = TrackPointIterator.class.getSimpleName();
private final ContentProviderUtils contentProviderUtils;
private final long trackId;
private final Track.Id trackId;
private final boolean descending;
private final CachedTrackPointsIndexes indexes;
private long lastTrackPointId = -1L;
private Cursor cursor;
public TrackPointIterator(ContentProviderUtils contentProviderUtils, long trackId, long startTrackPointId, boolean descending) {
public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, long startTrackPointId, boolean descending) {
this.contentProviderUtils = contentProviderUtils;
this.trackId = trackId;
this.descending = descending;
@@ -25,6 +25,7 @@ import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.DialogUtils;
/**
@@ -44,9 +45,9 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
*
* @param trackIds list of track ids to delete.
*/
public static void showDialog(FragmentManager fragmentManager, long[] trackIds) {
public static void showDialog(FragmentManager fragmentManager, Track.Id... trackIds) {
Bundle bundle = new Bundle();
bundle.putLongArray(KEY_TRACK_IDS, trackIds);
bundle.putParcelableArray(KEY_TRACK_IDS, trackIds);
ConfirmDeleteDialogFragment deleteTrackDialogFragment = new ConfirmDeleteDialogFragment();
deleteTrackDialogFragment.setArguments(bundle);
@@ -66,7 +67,7 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS);
final Track.Id[] trackIds = (Track.Id[]) getArguments().getParcelableArray(KEY_TRACK_IDS);
int titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
int messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message;
@@ -85,6 +86,6 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
*/
public interface ConfirmDeleteCaller {
void onConfirmDeleteDone(long[] trackIds);
void onConfirmDeleteDone(Track.Id... trackIds);
}
}
@@ -26,6 +26,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.DialogUtils;
@@ -41,9 +42,9 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
private DeleteMarkerCaller caller;
public static void showDialog(FragmentManager fragmentManager, long[] markerIds) {
public static void showDialog(FragmentManager fragmentManager, Waypoint.Id... waypointIds) {
Bundle bundle = new Bundle();
bundle.putLongArray(KEY_MARKER_IDS, markerIds);
bundle.putParcelableArray(KEY_MARKER_IDS, waypointIds);
DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment();
deleteMarkerDialogFragment.setArguments(bundle);
@@ -63,23 +64,23 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS);
final Waypoint.Id[] waypointIds = (Waypoint.Id[]) getArguments().getParcelableArray(KEY_MARKER_IDS);
final FragmentActivity fragmentActivity = getActivity();
int titleId;
int messageId;
if (markerIds.length == 1 && markerIds[0] == -1L) {
if (waypointIds == null) {
titleId = R.string.generic_delete_all_confirm_title;
messageId = R.string.marker_delete_all_confirm_message;
} else {
titleId = markerIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.marker_delete_one_confirm_title;
messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
titleId = waypointIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.marker_delete_one_confirm_title;
messageId = waypointIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
}
return DialogUtils.createConfirmationDialog(
fragmentActivity, titleId, getString(messageId), (dialog, which) -> new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
for (long markerId : markerIds) {
contentProviderUtils.deleteWaypoint(getContext(), markerId);
for (Waypoint.Id waypointId : waypointIds) {
contentProviderUtils.deleteWaypoint(getContext(), waypointId);
}
caller.onDeleteMarkerDone();
}).start());
@@ -209,7 +209,7 @@ public class ExportProgressDialogFragment extends DialogFragment {
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context);
//TODO Move to helper function
String fileName = track.getId() + "." + trackFileFormat.getExtension();
String fileName = track.getId().getId() + "." + trackFileFormat.getExtension();
// Overwrite a file if it exists; DocumentFile.createFile() creates a new file appending a suffix if the displayname already exists.
DocumentFile file = directory.findFile(fileName);
@@ -214,7 +214,7 @@ public class ImportProgressDialogFragment extends DialogFragment {
}
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
return trackImporter.importFile(inputStream) != -1L;
return trackImporter.importFile(inputStream) != null;
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
return false;
@@ -0,0 +1,93 @@
package de.dennisguse.opentracks.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
import de.dennisguse.opentracks.views.IntervalListView;
/**
* A fragment to display the intervals from recorded track.
*/
public class IntervalsRecordedFragment extends Fragment implements IntervalListView.IntervalListListener {
private static final String TAG = IntervalsRecordedFragment.class.getSimpleName();
private static final String TRACK_ID_KEY = "trackId";
private IntervalStatisticsModel viewModel;
private IntervalListView intervalListView;
private Track.Id trackId;
public static Fragment newInstance(Track.Id trackId) {
Bundle bundle = new Bundle();
bundle.putParcelable(TRACK_ID_KEY, trackId);
Fragment fragment = new IntervalsRecordedFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.intervals_recording, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
trackId = getArguments().getParcelable(TRACK_ID_KEY);
intervalListView = new IntervalListView(getActivity(), this);
intervalListView.setId(View.generateViewId());
LinearLayout linearLayout = view.findViewById(R.id.root_view);
linearLayout.removeAllViews();
linearLayout.addView(intervalListView);
viewModel = new ViewModelProvider(this).get(IntervalStatisticsModel.class);
intervalChanged(null);
}
@Override
public void onDestroy() {
super.onDestroy();
intervalListView.destroy();
intervalListView = null;
viewModel = null;
}
/**
* Update intervals through {@link IntervalStatisticsModel} view model.
*
* @param interval intervals will split in this interval if not null. If it's null then view model will use the default one.
*/
@Override
public void intervalChanged(@Nullable IntervalStatisticsModel.IntervalOption interval) {
if (viewModel == null || intervalListView == null) {
return;
}
LiveData<IntervalStatistics> liveData = viewModel.getIntervalStats(trackId, interval);
liveData.observe(getActivity(), intervalStatistics -> {
if (intervalStatistics != null) {
intervalListView.display(intervalStatistics.getIntervalList());
}
});
}
}
@@ -0,0 +1,127 @@
package de.dennisguse.opentracks.fragments;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
import de.dennisguse.opentracks.views.IntervalListView;
/**
* A fragment to display the intervals from recording track.
*/
public class IntervalsRecordingFragment extends Fragment implements IntervalListView.IntervalListListener {
private static final String TAG = IntervalsRecordingFragment.class.getSimpleName();
private static final String TRACK_ID_KEY = "trackId";
// Refreshing intervals stats it's not so demanding so 5 seconds is enough to balance performance and user experience.
private static final long UI_UPDATE_INTERVAL = 5 * UnitConversions.ONE_SECOND_MS;
private IntervalStatisticsModel viewModel;
private IntervalListView.IntervalReverseListView intervalListView;
private Track.Id trackId;
public static Fragment newInstance(Track.Id trackId) {
Bundle bundle = new Bundle();
bundle.putParcelable(TRACK_ID_KEY, trackId);
Fragment fragment = new IntervalsRecordingFragment();
fragment.setArguments(bundle);
return fragment;
}
private final Runnable intervalRunner = new Runnable() {
@Override
public void run() {
if (isResumed()) {
updateIntervals();
intervalHandler.postDelayed(intervalRunner, UI_UPDATE_INTERVAL);
}
}
};
private Handler intervalHandler;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.intervals_recording, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
trackId = getArguments().getParcelable(TRACK_ID_KEY);
intervalHandler = new Handler();
intervalListView = new IntervalListView.IntervalReverseListView(getActivity(), this);
intervalListView.setId(View.generateViewId());
LinearLayout linearLayout = view.findViewById(R.id.root_view);
linearLayout.removeAllViews();
linearLayout.addView(intervalListView);
viewModel = new ViewModelProvider(this).get(IntervalStatisticsModel.class);
}
@Override
public void onResume() {
super.onResume();
intervalHandler.post(intervalRunner);
}
@Override
public void onPause() {
super.onPause();
intervalHandler.removeCallbacks(intervalRunner);
}
@Override
public void onDestroy() {
super.onDestroy();
intervalListView.destroy();
intervalListView = null;
viewModel = null;
}
/**
* Update intervals through {@link IntervalStatisticsModel} view model.
*
* @param interval intervals will split in this interval if not null. If it's null then view model will use the default one.
*/
@Override
public void intervalChanged(@Nullable IntervalStatisticsModel.IntervalOption interval) {
if (viewModel == null || intervalListView == null) {
return;
}
LiveData<IntervalStatistics> liveData = viewModel.getIntervalStats(trackId, interval);
liveData.observe(getActivity(), intervalStatistics -> {
if (intervalStatistics != null) {
intervalListView.display(intervalStatistics.getIntervalList());
}
});
}
private void updateIntervals() {
intervalChanged(null);
}
}
@@ -93,9 +93,9 @@ public class MarkerDetailFragment extends Fragment {
}
};
public static MarkerDetailFragment newInstance(long markerId) {
public static MarkerDetailFragment newInstance(Waypoint.Id waypointId) {
Bundle bundle = new Bundle();
bundle.putLong(KEY_MARKER_ID, markerId);
bundle.putParcelable(KEY_MARKER_ID, waypointId);
MarkerDetailFragment fragment = new MarkerDetailFragment();
fragment.setArguments(bundle);
@@ -106,8 +106,8 @@ public class MarkerDetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long markerId = getArguments().getLong(KEY_MARKER_ID);
if (markerId == -1L) {
Waypoint.Id waypointId = getArguments().getParcelable(KEY_MARKER_ID);
if (waypointId == null) {
Log.d(TAG, "invalid marker id");
getParentFragmentManager().popBackStack();
return;
@@ -182,7 +182,7 @@ public class MarkerDetailFragment extends Fragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
long markerId = getArguments().getLong(KEY_MARKER_ID);
Waypoint.Id waypointId = getArguments().getParcelable(KEY_MARKER_ID); //TODO Should only happen in onCreate?
FragmentActivity fragmentActivity = getActivity();
Intent intent;
switch (item.getItemId()) {
@@ -191,7 +191,7 @@ public class MarkerDetailFragment extends Fragment {
return true;
case R.id.marker_detail_edit:
intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, waypointId);
startActivity(intent);
return true;
case R.id.marker_detail_share:
@@ -202,7 +202,7 @@ public class MarkerDetailFragment extends Fragment {
}
return true;
case R.id.marker_detail_delete:
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), new long[]{markerId});
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), waypointId);
return true;
default:
return super.onOptionsItemSelected(item);
@@ -216,7 +216,7 @@ public class MarkerDetailFragment extends Fragment {
*/
private void updateWaypoint(boolean refresh) {
if (refresh || waypoint == null) {
waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID));
waypoint = contentProviderUtils.getWaypoint(getArguments().getParcelable(KEY_MARKER_ID)); //TODO Should only happen in onCreate?
if (waypoint == null) {
Log.d(TAG, "waypoint is null");
getParentFragmentManager().popBackStack();
@@ -33,11 +33,8 @@ import androidx.fragment.app.Fragment;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
@@ -49,10 +46,13 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class StatisticsRecordedFragment extends Fragment implements TrackDataListener {
public class StatisticsRecordedFragment extends Fragment {
private TrackStatistics trackStatistics = null;
private static final String TRACK_ID_KEY = "trackId";
private TrackStatistics trackStatistics;
private String category = "";
private Track track;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
@@ -71,7 +71,6 @@ public class StatisticsRecordedFragment extends Fragment implements TrackDataLis
return inflater.inflate(R.layout.statistics_recorded, container, false);
}
/* Views */
private TextView totalTimeValueView;
private TextView distanceValue;
private TextView distanceUnit;
@@ -88,6 +87,24 @@ public class StatisticsRecordedFragment extends Fragment implements TrackDataLis
private TextView speedMovingValue;
private TextView speedMovingUnit;
public static StatisticsRecordedFragment newInstance(Track.Id trackId) {
Bundle bundle = new Bundle();
bundle.putParcelable(TRACK_ID_KEY, trackId);
StatisticsRecordedFragment fragment = new StatisticsRecordedFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Track.Id trackId = getArguments().getParcelable(TRACK_ID_KEY);
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getContext());
track = contentProviderUtils.getTrack(trackId);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -140,23 +157,15 @@ public class StatisticsRecordedFragment extends Fragment implements TrackDataLis
super.onResume();
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
TrackDataHub trackDataHub = ((TrackRecordedActivity) getActivity()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, true, false, true, true);
}
@Override
public void onPause() {
super.onPause();
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
TrackDataHub trackDataHub = ((TrackRecordedActivity) getActivity()).getTrackDataHub();
trackDataHub.unregisterTrackDataListener(this);
loadStatistics();
}
@Override
public void onDestroyView() {
super.onDestroyView();
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
totalTimeValueView = null;
distanceValue = null;
@@ -180,8 +189,7 @@ public class StatisticsRecordedFragment extends Fragment implements TrackDataLis
speedMovingUnit = null;
}
@Override
public void onTrackUpdated(final Track track) {
public void loadStatistics() {
if (isResumed()) {
getActivity().runOnUiThread(() -> {
if (isResumed()) {
@@ -193,41 +201,6 @@ public class StatisticsRecordedFragment extends Fragment implements TrackDataLis
}
}
@Override
public void clearTrackPoints() {
// We don't care.
}
@Override
public void onSampledInTrackPoint(TrackPoint trackPoint) {
// We don't care.
}
@Override
public void onSampledOutTrackPoint(TrackPoint trackPoint) {
// We don't care.
}
@Override
public void onNewTrackPointsDone() {
// We don't care.
}
@Override
public void clearWaypoints() {
// We don't care.
}
@Override
public void onNewWaypoint(Waypoint wpt) {
// We don't care.
}
@Override
public void onNewWaypointsDone() {
// We don't care.
}
private void updateUI() {
String trackIconValue = TrackIconUtils.getIconValue(getContext(), category);
@@ -55,13 +55,17 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
private TrackPoint lastTrackPoint = null;
private TrackStatistics lastTrackStatistics = null;
private TrackPoint lastTrackPoint;
private TrackStatistics lastTrackStatistics;
private String category = "";
@Deprecated //TODO This should be handled somewhere else; not in the UI.
private int recordingGpsAccuracy;
public static Fragment newInstance() {
return new StatisticsRecordingFragment();
}
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
@@ -76,7 +80,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
}
if (PreferencesUtils.isKey(getContext(), R.string.recording_track_id_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(getContext());
if (PreferencesUtils.getRecordingTrackId(getContext()) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
if (PreferencesUtils.getRecordingTrackId(getContext()).isValid()) {
// A recording track id has been set -> Resumes track and starts timer.
resumeTrackDataHub();
trackRecordingServiceConnection.startConnection(getContext());
@@ -98,7 +98,7 @@ public class FileTrackExporter implements TrackExporter {
* I am leaving the number of waypoints very high which should not be a problem, because we don't try to load them into objects all at the same time.
*/
boolean hasWaypoints = false;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), null, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) {
@@ -388,7 +388,7 @@ public class KmlTrackWriter implements TrackWriter {
* @param trackId the track id containing the location
* @param location the location
*/
private float getHeading(long trackId, Location location) {
private float getHeading(Track.Id trackId, Location location) {
long trackPointId = contentProviderUtils.getTrackPointId(trackId, location);
if (trackPointId == -1L) {
return location.getBearing();
@@ -76,10 +76,7 @@ public class KmzTrackExporter implements TrackExporter {
@Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
ZipOutputStream zipOutputStream = null;
try {
zipOutputStream = new ZipOutputStream(outputStream);
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
// Add kml file
ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE);
zipOutputStream.putNextEntry(zipEntry);
@@ -97,20 +94,12 @@ public class KmzTrackExporter implements TrackExporter {
} catch (InterruptedException | IOException e) {
Log.e(TAG, "Unable to write track", e);
return false;
} finally {
if (zipOutputStream != null) {
try {
zipOutputStream.close();
} catch (IOException e) {
Log.e(TAG, "Unable to close zip input stream", e);
}
}
}
}
private void addImages(Context context, Track[] tracks, ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
for (Track track : tracks) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, -1)) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) {
@@ -68,8 +68,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
private final ContentProviderUtils contentProviderUtils;
private final int recordingDistanceInterval;
private long importTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private final List<Long> trackIds = new ArrayList<>();
private Track.Id importTrackId;
private final List<Track.Id> trackIds = new ArrayList<>();
private final List<Waypoint> waypoints = new ArrayList<>();
// The current element content
@@ -104,7 +104,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
}
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils, long importTrackId) {
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils, Track.Id importTrackId) {
this(context, contentProviderUtils);
this.importTrackId = importTrackId;
}
@@ -126,7 +126,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
}
@Override
public long importFile(InputStream inputStream) {
public Track.Id importFile(InputStream inputStream) {
try {
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
long start = System.currentTimeMillis();
@@ -140,7 +140,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
} catch (MultiTracksImportException | IOException | SAXException | SQLiteConstraintException | ParserConfigurationException e) {
Log.e(TAG, "Unable to import file", e);
cleanImport();
return -1L;
return null;
}
}
@@ -153,7 +153,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (size == 0) {
return;
}
long trackId = trackIds.get(size - 1);
Track.Id trackId = trackIds.get(size - 1);
Track track = contentProviderUtils.getTrack(trackId);
if (track == null) {
return;
@@ -225,10 +225,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
*/
protected void onTrackStart() throws SAXException {
trackData = new TrackData();
long trackId;
if (importTrackId == -1L) {
Track.Id trackId;
if (importTrackId == null) {
Uri uri = contentProviderUtils.insertTrack(trackData.track);
trackId = Long.parseLong(uri.getLastPathSegment());
trackId = new Track.Id(Long.parseLong(uri.getLastPathSegment()));
} else {
if (trackIds.size() > 0) {
throw new SAXException(createErrorMessage("Cannot import more than one track to an existing track " + importTrackId));
@@ -399,7 +399,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* @param externalPhotoUrl the file name
*/
protected String getInternalPhotoUrl(String externalPhotoUrl) {
if (importTrackId == -1L) {
if (importTrackId == null) {
Log.e(TAG, "Track id is invalid.");
return null;
}
@@ -517,7 +517,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* Cleans up import.
*/
private void cleanImport() {
for (long trackId : trackIds) {
for (Track.Id trackId : trackIds) {
contentProviderUtils.deleteTrack(context, trackId);
}
}
@@ -32,6 +32,7 @@ import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
@@ -52,7 +53,7 @@ public class KmzTrackImporter implements TrackImporter {
private static final int BUFFER_SIZE = 4096;
private final Context context;
private long importTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
private Track.Id importTrackId; //TODO needed?
private final Uri uriKmzFile;
/**
@@ -65,18 +66,18 @@ public class KmzTrackImporter implements TrackImporter {
}
@Override
public long importFile(InputStream inputStream) {
long trackId;
public Track.Id importFile(InputStream inputStream) {
Track.Id trackId;
if (!copyKmzImages()) {
cleanImport(context, importTrackId);
return -1L;
return null;
}
trackId = findAndParseKmlFile(inputStream);
if (trackId == -1L) {
if (trackId == null) {
cleanImport(context, importTrackId);
return -1L;
return null;
}
deleteOrphanImages(context, trackId);
@@ -91,7 +92,7 @@ public class KmzTrackImporter implements TrackImporter {
*/
private boolean copyKmzImages() {
try (InputStream inputStream = context.getContentResolver().openInputStream(uriKmzFile);
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
@@ -140,7 +141,7 @@ public class KmzTrackImporter implements TrackImporter {
* Returns true if fileName ends with some of the KMZ_IMAGES_EXT suffixes.
* Otherwise returns false.
*/
private boolean hasImageExtension(String fileName) {
private boolean hasImageExtension(String fileName) {
if (fileName == null) {
return false;
}
@@ -155,27 +156,28 @@ public class KmzTrackImporter implements TrackImporter {
/**
* Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it.
* TODO: May load multiple tracks, but only returns the last Track.Id.
*
* @param inputStream kmz input stream.
* @return -1 if error or the id of the track otherwise.
* @return null if error or the id of the track otherwise.
*/
private long findAndParseKmlFile(InputStream inputStream) {
private Track.Id findAndParseKmlFile(InputStream inputStream) {
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry;
long trackId = -1L;
Track.Id trackId = null;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (Thread.interrupted()) {
Log.d(TAG, "Thread interrupted");
return -1L;
return null;
}
String fileName = zipEntry.getName();
if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) {
trackId = parseKml(zipInputStream);
if (trackId == -1L) {
if (trackId != null) {
Log.d(TAG, "Unable to parse kml in kmz");
return -1L;
return null;
}
}
@@ -184,7 +186,7 @@ public class KmzTrackImporter implements TrackImporter {
return trackId;
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
return -1L;
return null;
}
}
@@ -194,8 +196,8 @@ public class KmzTrackImporter implements TrackImporter {
* @param context the Context object.
* @param trackId the id of the Track.
*/
private void deleteOrphanImages(Context context, long trackId) {
if (trackId != 1L) {
private void deleteOrphanImages(Context context, Track.Id trackId) {
if (!trackId.isValid()) {
// 1.- Gets all photo names in the waypoints of the track identified by id.
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
List<Waypoint> waypoints = contentProviderUtils.getWaypoints(trackId);
@@ -227,7 +229,7 @@ public class KmzTrackImporter implements TrackImporter {
*
* @param trackId the trackId
*/
private void cleanImport(Context context, long trackId) {
private void cleanImport(Context context, Track.Id trackId) {
if (PreferencesUtils.isRecording(trackId)) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
contentProviderUtils.deleteTrack(context, trackId);
@@ -240,7 +242,7 @@ public class KmzTrackImporter implements TrackImporter {
* @param zipInputStream the zip input stream
* @return the imported track id or -1L
*/
private long parseKml(ZipInputStream zipInputStream) throws IOException {
private Track.Id parseKml(ZipInputStream zipInputStream) throws IOException {
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
@@ -271,7 +273,7 @@ public class KmzTrackImporter implements TrackImporter {
* @param fileName the file name
*/
private void readAndSaveImageFile(ZipInputStream zipInputStream, String fileName) throws IOException {
if (importTrackId == -1L || fileName.equals("")) {
if (importTrackId == null || fileName.equals("")) {
return;
}
@@ -17,6 +17,8 @@ package de.dennisguse.opentracks.io.file.importer;
import java.io.InputStream;
import de.dennisguse.opentracks.content.data.Track;
/**
* Interface for a track importer.
*
@@ -28,8 +30,8 @@ public interface TrackImporter {
* Import a file.
*
* @param inputStream the file's input stream
* @return the imported track id or RECORDING_TRACK_ID_DEFAULT.
* @return the imported track id or null.
*/
//TODO Figure out how can make the import an atomic operation (incl. database transaction rollback).
long importFile(InputStream inputStream);
Track.Id importFile(InputStream inputStream);
}
@@ -76,7 +76,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private PeriodicTaskExecutor voiceExecutor;
private TrackRecordingServiceNotificationManager notificationManager;
private long recordingTrackId;
private Track.Id recordingTrackId;
private boolean recordingTrackPaused;
private int recordingDistanceInterval;
private int maxRecordingDistance;
@@ -138,7 +138,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
notificationManager = new TrackRecordingServiceNotificationManager(this);
// onSharedPreferenceChanged might not set recordingTrackId.
recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
recordingTrackId = null;
PreferencesUtils.register(this, sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
@@ -204,7 +204,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return recordingTrackPaused;
}
public long getRecordingTrackId() {
public Track.Id getRecordingTrackId() {
return recordingTrackId;
}
@@ -230,9 +230,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
*
* @return the waypoint id
*/
public long insertWaypoint(String name, String category, String description, String photoUrl) {
public Waypoint.Id insertWaypoint(String name, String category, String description, String photoUrl) {
if (!isRecording() || isPaused()) {
return -1L;
return null;
}
if (name == null) {
@@ -246,7 +246,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId);
if (trackPoint == null) {
Log.i(TAG, "Could not create a waypoint as trackPoint is unknown.");
return -1L;
return null;
}
category = category != null ? category : "";
@@ -261,7 +261,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Insert waypoint
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, trackPoint.getLocation(), photoUrl);
Uri uri = contentProviderUtils.insertWaypoint(waypoint);
return ContentUris.parseId(uri);
return new Waypoint.Id(ContentUris.parseId(uri));
}
/**
@@ -269,10 +269,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
*
* @return the track id
*/
long startNewTrack() {
Track.Id startNewTrack() {
if (isRecording()) {
Log.d(TAG, "Ignore startNewTrack. Already recording.");
return -1L;
return null;
}
long now = System.currentTimeMillis();
trackStatisticsUpdater = new TrackStatisticsUpdater(now);
@@ -280,7 +280,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Insert a track
Track track = new Track();
Uri uri = contentProviderUtils.insertTrack(track);
long trackId = ContentUris.parseId(uri);
Track.Id trackId = new Track.Id(ContentUris.parseId(uri));
// Update shared preferences
updateRecordingState(trackId, false);
@@ -305,7 +305,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
*
* @param trackId the id of the track to be resumed.
*/
void resumeTrack(long trackId) {
void resumeTrack(Track.Id trackId) {
Track track = contentProviderUtils.getTrack(trackId);
if (track == null) {
Log.e(TAG, "Ignore resumeTrack. Track " + trackId + " does not exists.");
@@ -333,7 +333,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
if (track == null) {
if (isRecording()) {
Log.w(TAG, "track is null, but recordingTrackId not -1L. " + recordingTrackId);
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
updateRecordingState(null, true);
}
showNotification(false);
return;
@@ -403,10 +403,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
}
// Need to remember the recordingTrackId before setting it to -1L
long trackId = recordingTrackId;
Track.Id trackId = recordingTrackId;
boolean wasPaused = recordingTrackPaused;
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true);
updateRecordingState(null, true);
// Update database
Track track = contentProviderUtils.getTrack(trackId);
@@ -489,7 +489,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* @param trackId the track id
* @return the location or null
*/
private TrackPoint getLastValidTrackPointInCurrentSegment(long trackId) {
private TrackPoint getLastValidTrackPointInCurrentSegment(Track.Id trackId) {
if (!currentSegmentHasTrackPoint()) {
return null;
}
@@ -507,9 +507,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* @param trackId the recording track id
* @param paused true if the recording is paused
*/
private void updateRecordingState(long trackId, boolean paused) {
private void updateRecordingState(Track.Id trackId, boolean paused) {
recordingTrackId = trackId;
PreferencesUtils.setLong(this, R.string.recording_track_id_key, trackId);
long currentTrackId = trackId != null ? trackId.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
PreferencesUtils.setLong(this, R.string.recording_track_id_key, currentTrackId);
recordingTrackPaused = paused;
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused);
}
@@ -2,7 +2,9 @@ package de.dennisguse.opentracks.services;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
@@ -43,12 +45,12 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
}
@Override
public long startNewTrack() {
public Track.Id startNewTrack() {
return trackRecordingService.startNewTrack();
}
@Override
public void resumeTrack(long trackId) {
public void resumeTrack(Track.Id trackId) {
trackRecordingService.resumeTrack(trackId);
}
@@ -78,7 +80,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
}
@Override
public long getRecordingTrackId() {
public Track.Id getRecordingTrackId() {
return trackRecordingService.getRecordingTrackId();
}
@@ -88,7 +90,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
}
@Override
public long insertWaypoint(String name, String category, String description, String photoUrl) {
public Waypoint.Id insertWaypoint(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertWaypoint(name, category, description, photoUrl);
}
@@ -31,6 +31,8 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackEditActivity;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
@@ -177,18 +179,18 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
/**
* Adds a marker.
*
* @return the id of the marker or -1L if none could be created.
* @return the id of the marker or null if none could be created.
*/
public long addMarker(Context context, String name, String category, String description, String photoUrl) {
public Waypoint.Id addMarker(Context context, String name, String category, String description, String photoUrl) {
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) {
Log.d(TAG, "Unable to add marker, no track recording service");
} else {
try {
long markerId = trackRecordingService.insertWaypoint(name, category, description, photoUrl);
if (markerId != -1L) {
Waypoint.Id waypoint = trackRecordingService.insertWaypoint(name, category, description, photoUrl);
if (waypoint != null) {
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
return markerId;
return waypoint;
}
} catch (IllegalStateException e) {
Log.e(TAG, "Unable to add marker.", e);
@@ -196,7 +198,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
}
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
return -1L;
return null;
}
/**
@@ -213,7 +215,7 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
try {
if (showEditor) {
// Need to remember the recordingTrackId before calling endCurrentTrack() as endCurrentTrack() sets the value to -1L.
long recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
trackRecordingService.endCurrentTrack();
if (PreferencesUtils.isRecording(context)) {
Intent intent = IntentUtils.newIntent(context, TrackEditActivity.class)
@@ -17,7 +17,9 @@ package de.dennisguse.opentracks.services;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
@@ -43,12 +45,12 @@ public interface TrackRecordingServiceInterface {
*
* @return the track ID of the new track.
*/
long startNewTrack();
Track.Id startNewTrack();
/**
* Resumes the track identified by trackId.
*/
void resumeTrack(long trackId);
void resumeTrack(Track.Id trackId);
/**
* Pauses the current recording track.
@@ -76,13 +78,14 @@ public interface TrackRecordingServiceInterface {
boolean isPaused();
/**
* Gets the current recording track ID. Returns -1 if not recording.
* Gets the current recording track ID. Returns null if not recording.
*/
long getRecordingTrackId();
Track.Id getRecordingTrackId();
/**
* Gets the total time for the current recording track. Returns 0 if not recording.
*/
//TODO milliseconds?
long getTotalTime();
/**
@@ -90,7 +93,7 @@ public interface TrackRecordingServiceInterface {
*
* @return the ID of the inserted waypoint
*/
long insertWaypoint(String name, String category, String description, String photoUrl);
Waypoint.Id insertWaypoint(String name, String category, String description, String photoUrl);
/**
* Gets the current sensor data. Returns null if there is no data.
@@ -98,7 +98,7 @@ class GpsStatus {
/**
* This method must be called from the client every time a new location is received.
* Receive new location and calculatsrc/main/java/de/dennisguse/opentracks/services/handlers/LocationHandler.java the new status if needed.
* Receive new location and calculate the new status if needed.
* It look for GPS changes in lastLocation if it's not null. If it's null then look for in lastValidLocation if any.
*/
public void onLocationChanged(final Location location) {
@@ -42,8 +42,8 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
@Override
public void onStop(Context context) {
locationManager = null;
unregisterLocationListener();
locationManager = null;
if (gpsStatus != null) {
gpsStatus.stop();
gpsStatus = null;
@@ -22,14 +22,18 @@ import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import java.util.List;
import java.util.Locale;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.AnnouncementUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
/**
* This class will periodically announce the user's {@link TrackStatistics}.
@@ -159,9 +163,17 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
Log.i(TAG, "Speech is not allowed at this time.");
return;
}
Track track = contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context));
String category = track != null ? track.getCategory() : "";
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, category);
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(track.getId());
IntervalStatistics intervalStatistics = new IntervalStatistics();
intervalStatistics.build(trackPoints, (float) (PreferencesUtils.isMetricUnits(context) ? 1 * UnitConversions.KM_TO_M : 1 * UnitConversions.MI_TO_M));
int numIntervals = intervalStatistics.getIntervalList().size();
IntervalStatistics.Interval lastInterval = numIntervals > 0 ? intervalStatistics.getIntervalList().get(numIntervals - 1) : null;
String announcement = AnnouncementUtils.getAnnouncement(context, trackStatistics, category, lastInterval);
speakAnnouncement(announcement);
}
@@ -8,6 +8,8 @@ import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.ParcelUuid;
import android.text.TextUtils;
@@ -16,6 +18,7 @@ import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat;
@@ -189,7 +192,11 @@ public abstract class BluetoothLeSensorPreference extends DialogPreference {
BluetoothLeSensorPreferenceDialog.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
dialog.dismiss();
});
builder.setIcon(R.drawable.ic_bluetooth_searching_24dp);
Drawable icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_bluetooth_searching_animated_24dp);
((Animatable) icon).start();
builder.setIcon(icon);
builder.setPositiveButton(null, null);
}
@@ -2,18 +2,22 @@ package de.dennisguse.opentracks.util;
import android.content.Context;
import androidx.annotation.Nullable;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
public class AnnouncementUtils {
private AnnouncementUtils() {}
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, String category) {
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, String category, @Nullable IntervalStatistics.Interval currentInterval) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context, category);
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
double distancePerTime = trackStatistics.getAverageMovingSpeed() * UnitConversions.MPS_TO_KMH;
double currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH : 0;
if (distance == 0) {
return context.getString(R.string.voice_total_distance_zero);
@@ -22,23 +26,36 @@ public class AnnouncementUtils {
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
distancePerTime *= UnitConversions.KM_TO_MI;
currentDistancePerTime *= UnitConversions.KM_TO_MI;
}
String rate;
String currentRate;
String currentRateMsg = "";
if (reportSpeed) {
int speedId = metricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
currentRate = context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTime), currentDistancePerTime);
currentRateMsg = metricUnits ? context.getString(R.string.voice_speed_current_kilometer, currentRate) : context.getString(R.string.voice_speed_current_mile, currentRate);
} else {
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
long time = Math.round(timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
rate = context.getString(paceId, getAnnounceTime(context, time));
double currentTimePerDistance = currentDistancePerTime == 0 ? 0.0 : 1 / currentDistancePerTime;
long currentTime = Math.round(currentTimePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
currentRate = context.getString(paceId, getAnnounceTime(context, currentTime));
currentRateMsg = metricUnits ? context.getString(R.string.voice_pace_current_kilometer, currentRate) : context.getString(R.string.voice_pace_current_mile, currentRate);
}
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(context, trackStatistics.getMovingTime()), rate);
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
return context.getString(R.string.voice_template, totalDistance, getAnnounceTime(context, trackStatistics.getMovingTime()), rate) + currentRateMsg;
}
private static String getAnnounceTime(Context context, long time) {
@@ -33,6 +33,7 @@ import java.util.ArrayList;
import java.util.Date;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.content.data.Track;
/**
* Utilities for dealing with files.
@@ -63,8 +64,8 @@ public class FileUtils {
return context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
}
public static File getPhotoDir(Context context, long trackId) {
File photoDirectory = new File(getPhotoDir(context), "" + trackId);
public static File getPhotoDir(Context context, Track.Id trackId) {
File photoDirectory = new File(getPhotoDir(context), "" + trackId.getId());
photoDirectory.mkdirs();
return photoDirectory;
}
@@ -223,7 +224,7 @@ public class FileUtils {
* @param context the context.
* @param trackId the track id.
*/
public static String getImageUrl(Context context, long trackId) {
public static String getImageUrl(Context context, Track.Id trackId) {
File dir = FileUtils.getPhotoDir(context, trackId);
String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date());
@@ -267,7 +268,7 @@ public class FileUtils {
* @param uri the uri to check.
* @return File object or null.
*/
public static File getPhotoFileIfExists(Context context, long trackId, Uri uri) {
public static File getPhotoFileIfExists(Context context, Track.Id trackId, Uri uri) {
if (uri == null) {
Log.w(TAG, "URI object is null.");
return null;
@@ -8,7 +8,10 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
@@ -42,7 +45,7 @@ public class IntentDashboardUtils {
* @param context the context
* @param trackIds the track ids
*/
public static void startDashboard(Context context, long[] trackIds, boolean isRecording) {
public static void startDashboard(Context context, boolean isRecording, Track.Id... trackIds) {
if (trackIds.length == 0) {
return;
}
@@ -73,14 +76,14 @@ public class IntentDashboardUtils {
context.startActivity(intent);
}
public static long[] extractTrackIdsFromIntent(@NonNull Intent intent) {
public static Set<Track.Id> extractTrackIdsFromIntent(@NonNull Intent intent) {
final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD);
final Uri tracksUri = uris.get(TRACK_URI_INDEX);
String[] trackIdsString = ContentProviderUtils.parseTrackIdsFromUri(tracksUri);
long[] trackIds = new long[trackIdsString.length];
for (int i = 0; i < trackIdsString.length; i++) {
trackIds[i] = Long.parseLong(trackIdsString[i]);
Set<Track.Id> trackIds = new HashSet<>(trackIdsString.length);
for (String s : trackIdsString) {
trackIds.add(new Track.Id(Long.parseLong(s)));
}
return trackIds;
@@ -67,7 +67,7 @@ public class IntentUtils {
* @param context the context
* @param trackIds the track ids
*/
public static Intent newShareFileIntent(Context context, long[] trackIds) {
public static Intent newShareFileIntent(Context context, Track.Id... trackIds) {
if (trackIds.length == 0) {
throw new RuntimeException("Need to share at least one track.");
}
@@ -84,14 +84,14 @@ public class IntentUtils {
String mime = "";
ArrayList<Uri> uris = new ArrayList<>();
for (long trackId : trackIds) {
for (Track.Id trackId : trackIds) {
Track track = contentProviderUtils.getTrack(trackId);
if (track == null) {
Log.e(TAG, "TrackId " + trackId + " could not be resolved.");
continue;
}
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(new long[]{trackId}, track.getName(), PreferencesUtils.getExportTrackFileFormat(context));
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat(context));
uris.add(uriAndMime.first);
mime = uriAndMime.second;
}
@@ -154,7 +154,6 @@ public class IntentUtils {
}
/**
* Sends a take picture request to the camera app.
* The picture is then stored in the track's folder.
@@ -162,7 +161,7 @@ public class IntentUtils {
* @param context the context
* @param trackId the track id
*/
public static Pair<Intent, Uri> createTakePictureIntent(Context context, long trackId) {
public static Pair<Intent, Uri> createTakePictureIntent(Context context, Track.Id trackId) {
File dir = FileUtils.getPhotoDir(context, trackId);
String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date());
@@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceManager;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
/**
@@ -54,9 +55,9 @@ public class PreferencesUtils {
public static final long RECORDING_TRACK_ID_DEFAULT = -1L;
@Deprecated //Use the TrackRecordingService
public static long getRecordingTrackId(Context context) {
public static Track.Id getRecordingTrackId(Context context) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
return sharedPreferences.getLong(getKey(context, R.string.recording_track_id_key), RECORDING_TRACK_ID_DEFAULT);
return new Track.Id(sharedPreferences.getLong(getKey(context, R.string.recording_track_id_key), RECORDING_TRACK_ID_DEFAULT));
}
public static String getDefaultActivity(Context context) {
@@ -347,14 +348,16 @@ public class PreferencesUtils {
@Deprecated //Use TrackRecordingService
public static boolean isRecording(Context context) {
long recordingTrackId = getRecordingTrackId(context);
return isRecording(recordingTrackId);
return isRecording(getRecordingTrackId(context));
}
@Deprecated
//TODO Method is very misleading: it only checks if the provided trackId not the default value (i.e., not recording).
public static boolean isRecording(long recordingTrackId) {
return recordingTrackId != RECORDING_TRACK_ID_DEFAULT;
public static boolean isRecording(Track.Id recordingTrackId) {
if (recordingTrackId == null) {
return false;
}
return recordingTrackId.getId() != RECORDING_TRACK_ID_DEFAULT;
}
public static void resetPreferences(Context context, boolean readAgain) {
@@ -17,26 +17,19 @@ package de.dennisguse.opentracks.util;
import android.content.Context;
import android.location.Location;
import android.os.Build;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Pair;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.dennisguse.opentracks.R;
@@ -52,23 +45,6 @@ public class StringUtils {
private static final String COORDINATE_DEGREE = "\u00B0";
//TODO Remove when upgrading to API level 26+.
@Deprecated
private static final SimpleDateFormat ISO_8601_DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
//TODO Remove when upgrading to API level 26+.
@Deprecated
private static final SimpleDateFormat ISO_8601_BASE = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
//TODO Remove when upgrading to API level 26+.
@Deprecated
private static final Pattern ISO_8601_EXTRAS = Pattern.compile("^(\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))?$");
static {
ISO_8601_DATE_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
ISO_8601_BASE.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private StringUtils() {
}
@@ -84,17 +60,12 @@ public class StringUtils {
}
/**
* Formats the time using the ISO 8601 date time_ms format with fractional
* seconds in UTC time zone.
* Formats the time using the ISO 8601 date time_ms format with fractional seconds in UTC time zone.
*
* @param time_ms the time in milliseconds
*/
public static String formatDateTimeIso8601(long time_ms) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return Instant.ofEpochMilli(time_ms).toString();
} else {
return ISO_8601_DATE_TIME_FORMAT.format(time_ms);
}
return Instant.ofEpochMilli(time_ms).toString();
}
/**
@@ -292,72 +263,17 @@ public class StringUtils {
* @param xmlDateTime the XML date time string
*/
public static long parseTime(String xmlDateTime) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
TemporalAccessor t = DateTimeFormatter.ISO_DATE_TIME.parseBest(xmlDateTime, ZonedDateTime::from, LocalDateTime::from);
if (t instanceof LocalDateTime) {
Log.w(TAG, "Date does not contain timezone information: using UTC.");
t = ((LocalDateTime) t).atZone(ZoneOffset.UTC);
}
return Instant.from(t).toEpochMilli();
} catch (Exception e) {
Log.e(TAG, "Invalid XML dateTime value");
throw e;
try {
TemporalAccessor t = DateTimeFormatter.ISO_DATE_TIME.parseBest(xmlDateTime, ZonedDateTime::from, LocalDateTime::from);
if (t instanceof LocalDateTime) {
Log.w(TAG, "Date does not contain timezone information: using UTC.");
t = ((LocalDateTime) t).atZone(ZoneOffset.UTC);
}
return Instant.from(t).toEpochMilli();
} catch (Exception e) {
Log.e(TAG, "Invalid XML dateTime value");
throw e;
}
//TODO Remove the following when upgrading to API level 26+.
// Parse the date time base
ParsePosition position = new ParsePosition(0);
Date date = ISO_8601_BASE.parse(xmlDateTime, position);
if (date == null) {
throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime + " (at position " + position.getErrorIndex() + ")");
}
// Parse the date time extras
Matcher matcher = ISO_8601_EXTRAS.matcher(xmlDateTime.substring(position.getIndex()));
if (!matcher.matches()) {
// This will match even an empty string as all groups are optional. Thus a non-match means invalid content.
throw new IllegalArgumentException("Invalid XML dateTime value: " + xmlDateTime);
}
long time = date.getTime();
// Account for fractional seconds
String fractional = matcher.group(1);
if (fractional != null) {
// Regex ensures fractional part is in (0,1)
float fractionalSeconds = Float.parseFloat(fractional);
long fractionalMillis = Math.round(fractionalSeconds * UnitConversions.S_TO_MS);
time += fractionalMillis;
}
// Account for timezones
String sign = matcher.group(2);
String offsetHoursStr = matcher.group(3);
String offsetMinsStr = matcher.group(4);
if (sign != null && offsetHoursStr != null && offsetMinsStr != null) {
// Regex ensures sign is + or -
boolean plusSign = sign.equals("+");
int offsetHours = Integer.parseInt(offsetHoursStr);
int offsetMins = Integer.parseInt(offsetMinsStr);
// Regex ensures values are >= 0
if (offsetHours > 14 || offsetMins > 59) {
throw new IllegalArgumentException("Bad timezone: " + xmlDateTime);
}
long totalOffsetMillis = (offsetMins + offsetHours * 60L) * 60000L;
// Convert to UTC
if (plusSign) {
time -= totalOffsetMillis;
} else {
time += totalOffsetMillis;
}
}
return time;
}
/**
@@ -24,6 +24,7 @@ import java.text.SimpleDateFormat;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
/**
* Utilities for track name.
@@ -46,7 +47,7 @@ public class TrackNameUtils {
* @param trackId the track id
* @param startTime the track start time
*/
public static String getTrackName(Context context, long trackId, long startTime) {
public static String getTrackName(Context context, Track.Id trackId, long startTime) {
String trackName = PreferencesUtils.getString(context, R.string.track_name_key, context.getString(R.string.track_name_default));
if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_local_value))) {
@@ -54,7 +55,7 @@ public class TrackNameUtils {
} else if (trackName.equals(context.getString(R.string.settings_recording_track_name_date_iso_8601_value))) {
return new SimpleDateFormat(ISO_8601_FORMAT, Locale.US).format(startTime);
} else {
return context.getString(R.string.track_name_format, trackId);
return context.getString(R.string.track_name_format, trackId.getId());
}
}
}
@@ -65,6 +65,10 @@ public class UnitConversions {
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
// multiplication factor to convert meters per second to kilometers per hour
public static final double MPS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR);
// multiplication factor to convert miles to km
public static final double MI_TO_KM = 1 / KM_TO_MI;
// multiplication factor to convert miles to m
public static final double MI_TO_M = MI_TO_KM * KM_TO_M;
private UnitConversions() {
}
@@ -31,7 +31,7 @@ public class AggregatedStatisticsModel extends AndroidViewModel {
private void loadAggregatedStats() {
new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
List<Track> tracks = contentProviderUtils.getAllTracks();
List<Track> tracks = contentProviderUtils.getTracks();
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
aggregatedStatistics.aggregate(tracks);
@@ -0,0 +1,89 @@
package de.dennisguse.opentracks.viewmodels;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.UnitConversions;
public class IntervalStatistics {
List<Interval> intervalList = new ArrayList<>();
/**
* @param trackPointList the list of TrackPoint.
* @param distanceInterval_m the meters of every interval.
*/
public void build(List<TrackPoint> trackPointList, float distanceInterval_m) {
intervalList.clear();
if (trackPointList == null || trackPointList.size() == 0) {
return;
}
Interval interval = new Interval();
for (int i = 1; i < trackPointList.size(); i++) {
TrackPoint prevTrackPoint = trackPointList.get(i - 1);
TrackPoint trackPoint = trackPointList.get(i);
if (LocationUtils.isValidLocation(trackPoint.getLocation()) && LocationUtils.isValidLocation(prevTrackPoint.getLocation())) {
interval.distance_m += prevTrackPoint.distanceTo(trackPoint);
interval.time_ms += trackPoint.getTime() - prevTrackPoint.getTime();
if (interval.distance_m >= distanceInterval_m) {
float adjustFactor = distanceInterval_m / interval.distance_m;
Interval adjustedInterval = new Interval(interval);
adjustedInterval.adjust(adjustFactor);
intervalList.add(adjustedInterval);
interval = new Interval(interval.distance_m - adjustedInterval.distance_m, interval.time_ms - adjustedInterval.time_ms);
}
}
}
if (interval.distance_m > 1f) {
intervalList.add(interval);
}
}
public List<Interval> getIntervalList() {
return intervalList;
}
public static class Interval {
private float distance_m = 0f;
private float time_ms = 0f;
public Interval() {}
public Interval(float distance_m, float time_ms) {
this.distance_m = distance_m;
this.time_ms = time_ms;
}
public Interval(Interval i) {
distance_m = i.distance_m;
time_ms = i.time_ms;
}
public float getDistance_m() {
return distance_m;
}
public void adjust(float adjustFactor) {
distance_m *= adjustFactor;
time_ms *= adjustFactor;
}
/**
* @return speed of the interval in m/s.
*/
public float getSpeed_ms() {
if (distance_m == 0f) {
return 0f;
}
return distance_m / (float) (time_ms * UnitConversions.MS_TO_S);
}
}
}
@@ -0,0 +1,79 @@
package de.dennisguse.opentracks.viewmodels;
import android.app.Application;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* This view model is used to load intervals for a track.
* It uses a default interval but it can be set from outside to manage the interval length.
*/
public class IntervalStatisticsModel extends AndroidViewModel {
private MutableLiveData<IntervalStatistics> intervalStats = new MutableLiveData<>();
public IntervalStatisticsModel(@NonNull Application application) {
super(application);
}
public LiveData<IntervalStatistics> getIntervalStats(@Nullable Track.Id trackId, @Nullable IntervalOption interval) {
if (interval == null) {
interval = IntervalOption.OPTION_1;
}
if (trackId != null) {
loadIntervalStats(trackId, interval);
}
return intervalStats;
}
private void loadIntervalStats(final Track.Id trackId, IntervalOption interval) {
new Thread(() -> {
Context context = getApplication().getApplicationContext();
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
List<TrackPoint> trackPointList = contentProviderUtils.getTrackPoints(trackId);
IntervalStatistics intervalStatistics = new IntervalStatistics();
float distanceInterval = PreferencesUtils.isMetricUnits(context) ? (float) (interval.getValue() * UnitConversions.KM_TO_M) : (float) (interval.getValue() * UnitConversions.MI_TO_M);
intervalStatistics.build(trackPointList, distanceInterval);
intervalStats.postValue(intervalStatistics);
}).start();
}
/**
* Intervals length this view model support.
*/
public enum IntervalOption {
OPTION_1(1),
OPTION_2(2),
OPTION_3(3),
OPTION_4(4),
OPTION_5(5),
OPTION_10(10),
OPTION_20(20),
OPTION_50(50);
private int value;
IntervalOption(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
}
@@ -0,0 +1,120 @@
package de.dennisguse.opentracks.views;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
/**
* LinearLayout view used to build a list of intervals.
* See {@link IntervalStatisticsAdapter}.
*/
public class IntervalListView extends LinearLayout {
protected IntervalStatisticsAdapter adapter;
protected LinearLayout linearLayoutIntervals;
protected Spinner spinnerIntervals;
protected TextView spinnerIntervalsUnit;
protected IntervalListListener listener;
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
if (spinnerIntervalsUnit != null) {
spinnerIntervalsUnit.setText(PreferencesUtils.isMetricUnits(getContext()) ? getContext().getString(R.string.unit_kilometer) : getContext().getString(R.string.unit_mile));
}
}
};
public IntervalListView(Context context, IntervalListListener listener) {
super(context);
this.listener = listener;
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
inflate(getContext(), R.layout.interval_list_view, this);
linearLayoutIntervals = findViewById(R.id.interval_list);
spinnerIntervals = findViewById(R.id.spinner_intervals);
int[] intValues = Arrays.stream(IntervalStatisticsModel.IntervalOption.values()).mapToInt(i -> i.getValue()).toArray();
spinnerIntervals.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, Arrays.stream(intValues).mapToObj(String::valueOf).toArray(String[]::new)));
spinnerIntervalsUnit = findViewById(R.id.spinner_intervals_unit);
spinnerIntervalsUnit.setText(PreferencesUtils.isMetricUnits(getContext()) ? getContext().getString(R.string.unit_kilometer) : getContext().getString(R.string.unit_mile));
spinnerIntervals.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
IntervalStatisticsModel.IntervalOption interval = IntervalStatisticsModel.IntervalOption.values()[i];
listener.intervalChanged(interval);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
}
public void destroy() {
PreferencesUtils.unregister(getContext(), sharedPreferenceChangeListener);
adapter = null;
linearLayoutIntervals = null;
spinnerIntervals = null;
spinnerIntervalsUnit = null;
listener = null;
}
public void display(List<IntervalStatistics.Interval> intervalList) {
if (intervalList == null) {
return;
}
adapter = new IntervalStatisticsAdapter(getContext(), intervalList);
linearLayoutIntervals.removeAllViews();
for (int i = 0; i < adapter.getCount(); i++) {
View intervalView = adapter.getView(i, null, linearLayoutIntervals);
linearLayoutIntervals.addView(intervalView);
}
}
public interface IntervalListListener {
void intervalChanged(IntervalStatisticsModel.IntervalOption interval);
}
/**
* LinearLayout view used to build a list of intervals in a reverse mode, the last one will appear in the first position on the LinearLayout.
* This class is an specialization of {@link IntervalListView} that display the views contained in the LinearLayout in a reverse mode.
*/
public static class IntervalReverseListView extends IntervalListView {
public IntervalReverseListView(Context context, IntervalListListener listener) {
super(context, listener);
}
public void display(List<IntervalStatistics.Interval> intervalList) {
if (intervalList == null) {
return;
}
adapter = new IntervalStatisticsAdapter(getContext(), intervalList);
linearLayoutIntervals.removeAllViews();
for (int i = 0; i < adapter.getCount(); i++) {
View intervalView = adapter.getView(i, null, linearLayoutIntervals);
linearLayoutIntervals.addView(intervalView, 0);
}
}
}
}
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<objectAnimator
android:duration="650"
android:interpolator="@android:interpolator/linear"
android:propertyName="fillAlpha"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:startOffset="400"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</set>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<objectAnimator
android:duration="650"
android:interpolator="@android:interpolator/linear"
android:propertyName="fillAlpha"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</set>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_bluetooth_searching_24dp">
<target
android:name="smallDot"
android:animation="@anim/bluetooth_searching_smalldot" />
<target
android:name="bigDot"
android:animation="@anim/bluetooth_searching_bigdot" />
</animated-vector>
+36 -1
View File
@@ -1,5 +1,40 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_button_stop" >
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:width="82dp"
android:height="82dp"
android:viewportWidth="125"
android:viewportHeight="125">
<path
android:fillAlpha="1"
android:fillColor="#00000000"
android:pathData="M2.516,63.008a60.239,59.73 134.879,1 0,119.968 -1.017a60.239,59.73 134.879,1 0,-119.968 1.017z"
android:strokeWidth="5.03194237"
android:strokeAlpha="1"
android:strokeColor="#555555" />
<path
android:fillAlpha="1"
android:fillColor="#484848"
android:pathData="M62.5,62.5m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"
android:strokeWidth="6.9172821"
android:strokeAlpha="1"
android:strokeColor="#00000000" />
<path
android:fillAlpha="1"
android:fillColor="#ffffff"
android:fillType="nonZero"
android:pathData="M53.02,54.02h17.46v17.46h-17.46z"
android:strokeWidth="1.04023623"
android:strokeAlpha="1"
android:strokeColor="#414141" />
<path android:name="fade_out_circle"
android:pathData="M62.5,62.5m-0,-62a50,50 0,1 1,-0 123a50,50 0,1 1,-0 -123"
android:fillAlpha="0.0"
android:fillColor="#a31a1f">
</path>
</vector>
</aapt:attr>
<target
android:name="fade_out_circle"
android:animation="@animator/button_animator" />
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stats_split_interval"
style="@style/TextMedium" />
<Spinner
android:id="@+id/spinner_intervals"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SpinnerCustomText"
android:gravity="center" />
<TextView
android:id="@+id/spinner_intervals_unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/unit_kilometer"
style="@style/TextMedium" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/interval_distance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/TextMedium"
android:layout_margin="1dp"
android:textAlignment="center"
android:layout_gravity="center"
android:text="@string/stats_distance" />
<TextView
android:id="@+id/interval_speed"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/TextMedium"
android:layout_margin="1dp"
android:textAlignment="center"
android:layout_gravity="center"
android:text="@string/stats_speed" />
<TextView
android:id="@+id/interval_pace"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/TextMedium"
android:layout_margin="1dp"
android:textAlignment="center"
android:layout_gravity="center"
android:text="@string/stats_pace" />
</LinearLayout>
<LinearLayout
android:id="@+id/interval_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>
@@ -0,0 +1,39 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/interval_item_distance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/TextMedium"
tools:text="1 km" />
<TextView
android:id="@+id/interval_item_speed"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/TextMedium"
tools:text="15 km/h" />
<TextView
android:id="@+id/interval_item_pace"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/TextMedium"
tools:text="4:00" />
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/StatsScrollView">
<LinearLayout
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp" />
</ScrollView>
@@ -209,5 +209,24 @@
app:layout_constraintRight_toLeftOf="@+id/guideline3"
tools:text="km/h" />
<!-- Horizontal line -->
<View
android:id="@+id/stats_speed_horizontal_end_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintLeft_toRightOf="@+id/guideline"
app:layout_constraintRight_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/stats_max_speed_value" />
<LinearLayout
android:id="@+id/linear_layout_extra"
android:orientation="vertical"
android:layout_marginBottom="16dp"
app:layout_constraintLeft_toRightOf="@+id/guideline"
app:layout_constraintRight_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/stats_speed_horizontal_end_line"
android:layout_width="0dp"
android:layout_height="wrap_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
+4
View File
@@ -497,6 +497,10 @@ g) haz lo que creas que ayude a OpenTracks.</string>
<string name="voice_speed_miles_per_hour_two">2 millas por hora</string>
<string name="voice_speed_miles_per_hour_zero">0 millas por hora</string>
<string name="voice_template">OpenTracks de %1$s en %2$s a %3$s</string>
<string name="voice_speed_current_kilometer">Con una velocidad del kilómetro actual de %1$s</string>
<string name="voice_speed_current_mile">Con una velocidad de la milla actual de %1$s</string>
<string name="voice_pace_current_kilometer">Con un ritmo del kilómetro actual de %1$s</string>
<string name="voice_pace_current_mile">Con un ritmo de la milla actual de %1$s</string>
<string name="voice_total_distance_kilometers_few">distancia total de %1$.2f kilómetros</string>
<string name="voice_total_distance_kilometers_many">distancia total de %1$.2f kilómetros</string>
<string name="voice_total_distance_kilometers_one">distancia total de 1 kilómetro</string>
+7
View File
@@ -417,6 +417,8 @@ limitations under the License.
<string name="stats_speed">Speed</string>
<string name="stats_distance">Distance</string>
<string name="stats_total_time">Total time</string>
<string name="stats_intervals">Intervals</string>
<string name="stats_split_interval">Split interval</string>
<!-- Track -->
<string name="track_name_format">Track %1$d</string>
<string name="track_record_notification">Recording your track&#8230;</string>
@@ -430,6 +432,7 @@ limitations under the License.
<!-- Track Detail -->
<string name="track_detail_chart_tab">Chart</string>
<string name="track_detail_stats_tab">Stats</string>
<string name="track_detail_intervals_tab">Intervals</string>
<string name="track_detail_record_success">Now recording a new track</string>
<string name="track_detail_resume_success">Now resuming the track</string>
<!-- Track Edit -->
@@ -518,6 +521,10 @@ limitations under the License.
<string name="voice_speed_miles_per_hour_two">2 miles per hour</string>
<string name="voice_speed_miles_per_hour_zero">0 miles per hour</string>
<string name="voice_template">OpenTracks %1$s in %2$s at %3$s</string>
<string name="voice_speed_current_kilometer">With a current kilometer speed of %1$s</string>
<string name="voice_speed_current_mile">With a current mile speed of %1$s</string>
<string name="voice_pace_current_kilometer">With a current kilometer pace of %1$s</string>
<string name="voice_pace_current_mile">With a current mile pace of %1$s</string>
<string name="voice_total_distance_kilometers_few">total distance %1$.2f kilometers</string>
<string name="voice_total_distance_kilometers_many">total distance %1$.2f kilometers</string>
<string name="voice_total_distance_kilometers_one">total distance 1 kilometer</string>

Some files were not shown because too many files have changed in this diff Show More