Compare commits

...

62 Commits

Author SHA1 Message Date
Dennis Guse 80dbbbc1ed Release: v3.9.5 2020-09-06 19:51:34 +02:00
Dennis Guse 81e8872a11 Added script for creating releases. 2020-09-06 19:49:42 +02:00
Román 4220c62ac0 Bugfix: interval recording fragment has to keep select interval. 2020-09-06 12:21:39 +02:00
Dennis Guse 4b73567075 Bugfix: ClassCastException of Marker.Id 2020-09-06 08:06:29 +02:00
Dennis Guse fdff0d255a Autoformated drawables. 2020-09-06 00:09:39 +02:00
Dennis Guse e956cf14c4 Present speed only with one fractional digit. 2020-09-05 20:53:55 +02:00
Román a1646e1b76 Bugfix: bad equal comparison between Track.Id objects. 2020-09-05 15:46:49 +02:00
Dennis Guse 9d6c9676ab Refactoring to marker: content-package and fragments.
#389
2020-09-05 10:37:41 +02:00
Dennis Guse 19d8563a76 Refactoring to marker: TrackRecordingService.
#389
2020-09-05 10:20:16 +02:00
Dennis Guse 74e09e1fa9 Refactoring to marker: data class and ContentProviderUtils.
#389
2020-09-05 10:20:16 +02:00
Román 6533e3e170 Recording intervals received a null trackId because trackId is created in a Runnable.
Unit changed needs refreshing the intervals.
Fixes #406.
2020-09-05 10:18:01 +02:00
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
Dennis Guse 337e5b9e4d Release v3.9.1 2020-08-19 18:13:10 +02:00
Román 1ff986f1e6 Gps Status: bugfixes.
Bugfix 1: race condition issue fixed with a flag.
Bugfix 2: location time issue fixed setting the time every time a location is received.
2020-08-19 14:02:47 +02:00
thrillfall 6412bb9fcd remove useless parameter 2020-08-18 16:37:44 +02:00
thrillfall 8425997c29 add overload for exporter that takes single track 2020-08-18 16:37:44 +02:00
thrillfall f4b585b830 do not pass complete track data to exporter factory method just to knwo if there is more then one track to export 2020-08-18 16:37:44 +02:00
thrillfall 47c259fa9a move tracks array from class state to method argument 2020-08-18 16:37:44 +02:00
thrillfall 80208e3188 move condition for writing multitrack begin and end outside of methods 2020-08-18 16:37:44 +02:00
thrillfall 2655d3e773 fix decorator TrackExporter interface 2020-08-18 16:37:44 +02:00
160 changed files with 5321 additions and 2866 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.
Executable
+51
View File
@@ -0,0 +1,51 @@
#!/bin/bash
set -e
CHANGELOG_DIR="fastlane/metadata/android/en-US/changelogs/"
echo "Checking main branch"
git checkout main
VERSIONID=$(git rev-list HEAD --count main)
CHANGELOG_FILE="$CHANGELOG_DIR/$VERSIONID.txt"
git diff --exit-code || echo -e "\nWARNING WARNING WARNING WARNING WARNING: this branch contains uncommit changes."
#TODO Execute all tests: we are destroying data, running on a real device.
# ./gradlew connectedCheck
# Changelog
echo -e "\nPrevious changelog\n------------"
PREVIOUS_CHANGELOG=$(find "$CHANGELOG_DIR" -iname "[[:digit:]]**.txt" | sort -n | tail -1)
sed 's/^/>/' "$PREVIOUS_CHANGELOG"
#For F-Droid, a version starts with a 'v'
read -p $'\n\nPlease enter the versionname (e.g., v3.9.5):\n> v' VERSIONNAME
VERSIONNAME="v$VERSIONNAME"
cp "$CHANGELOG_DIR/next_release.txt" "$CHANGELOG_FILE"
sed -i "s/(versionName)/$VERSIONNAME/" "$CHANGELOG_FILE"
echo -e "\nGit commit since last release\n------------"
git log $(git describe --tags --abbrev=0)..HEAD --no-merges --oneline
echo -e "\nPlease edit the changelog for the new release"
gedit -w "$CHANGELOG_FILE"
git add "$CHANGELOG_FILE"
echo "Updating build.gradle"
sed -i s/versionCode\ .*/versionCode\ "$VERSIONID"/g build.gradle
sed -i s/versionName\ .*/versionName\ "\"$VERSIONNAME\""/g build.gradle
git diff
COMMIT_MESSAGE="Release: $VERSIONNAME"
echo -e "\nCommit message will be: '$COMMIT_MESSAGE'"
echo -e "\nPress ENTER, if everything looks good."
read
git add -u
git commit -m "$COMMIT_MESSAGE"
echo -e "Next steps:\n1. Push to Github\n2. Create release on Github: https://github.com/OpenTracksApp/OpenTracks/releases"
+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 3522
versionName "v3.9.0"
versionCode 3584
versionName "v3.9.5"
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,4 @@
v3.9.1: OpenTracks
Bugfixes:
- GPS pre-start shows status of fix
@@ -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
@@ -0,0 +1,8 @@
v3.9.5: OpenTracks
Changes:
- UI shows speed only with one fractional digit
Bugfix:
- App crash in search (due to Track.Id refactoring)
- Track list did now show if a recording was ongoing (due to Track.Id refactoring)
@@ -1,4 +1,4 @@
(tbd): OpenTracks
(versionName): OpenTracks
Changes:
-
@@ -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;
@@ -35,13 +34,17 @@ import java.util.List;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.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,47 +91,47 @@ 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 Marker.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);
if (hoursAgo >= 0) {
location.setTime(NOW - hoursAgo * 1000L * 60L * 60L);
}
Waypoint waypoint = new Waypoint(location);
Marker waypoint = new Marker(location);
waypoint.setName(title);
waypoint.setDescription(description);
waypoint.setCategory(category);
waypoint.setTrackId(trackId);
Uri uri = providerUtils.insertWaypoint(waypoint);
return ContentUris.parseId(uri);
Uri uri = providerUtils.insertMarker(waypoint);
return new Marker.Id(ContentUris.parseId(uri));
}
private long insertWaypoint(String title, String description, String category) {
return insertWaypoint(title, description, category, 0.0, -1, -1);
private Marker.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 Marker.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 Marker.Id insertWaypoint(String title, long hoursAgo) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, null);
}
private long insertWaypoint(String title, long hoursAgo, long trackId) {
private Marker.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");
Marker.Id descriptionMatchId = insertWaypoint("bb", "aa", "cc");
Marker.Id categoryMatchId = insertWaypoint("bb", "cc", "aa");
Marker.Id titleMatchId = insertWaypoint("aa", "bb", "cc");
Marker.Id titleCategoryMatchId = insertWaypoint("aa", "bb", "ca");
Marker.Id titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc");
Marker.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");
Marker.Id descriptionWaypointId = insertWaypoint("bb", "aa", "cc");
insertWaypoint("bb", "cc", "dd");
long titleWaypointId = insertWaypoint("aa", "bb", "cc");
Marker.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);
Marker.Id farFarAwayId = insertWaypoint("aa", 0.3);
Marker.Id nearId = insertWaypoint("ab", 0.1);
Marker.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);
Marker.Id oldestId = insertWaypoint("aa", 2);
Marker.Id recentId = insertWaypoint("ab", 0);
Marker.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);
Marker.Id otherId = insertWaypoint("aa", 1, new Track.Id(456));
Marker.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.marker);
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.marker);
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(Marker.Id waypointId, ScoredResult result) {
assertNotNull("Not a waypoint", result.marker);
assertNull("Ambiguous result", result.track);
assertEquals(waypointId, result.marker.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, Marker.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.marker);
assertNull(errMsg, result.track);
assertEquals(errMsg, waypointIds[i], result.marker.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,12 +93,12 @@ 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 Marker 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);
String photoUrl = photoUri.toString();
return new Waypoint("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl);
return new Marker("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl);
}
}
@@ -25,9 +25,9 @@ import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.Test;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -67,7 +67,7 @@ public class CustomContentProviderTest {
assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI_BY_ID));
assertEquals(TrackPointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_TRACKID.buildUpon(), 1).build()));
assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
assertEquals(MarkerColumns.CONTENT_TYPE, customContentProvider.getType(MarkerColumns.CONTENT_URI));
assertEquals(MarkerColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(MarkerColumns.CONTENT_URI.buildUpon(), 1).build()));
}
}
@@ -18,9 +18,9 @@ import org.junit.runner.RunWith;
import java.util.HashMap;
import java.util.Map;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -45,7 +45,7 @@ public class CustomSQLiteOpenHelperTest {
@VisibleForTesting
public static Map<String, String> getTableSQL(SQLiteDatabase db) {
HashMap<String, String> tableSQL = new HashMap<>();
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + WaypointsColumns.TABLE_NAME + "')", null, null, null, "name")) {
try (Cursor cursor = db.query("sqlite_master", new String[]{"name", "SQL"}, "name IN ('" + TracksColumns.TABLE_NAME + "', '" + TrackPointsColumns.TABLE_NAME + "', '" + MarkerColumns.TABLE_NAME + "')", null, null, null, "name")) {
if (cursor != null) {
while (cursor.moveToNext()) {
tableSQL.put(cursor.getString(0), cursor.getString(1));
@@ -106,8 +106,8 @@ public class CustomSQLiteOpenHelperTest {
assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE));
assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE_INDEX));
assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE));
assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE_INDEX));
assertTrue(hasSqlCreate(db, MarkerColumns.CREATE_TABLE));
assertTrue(hasSqlCreate(db, MarkerColumns.CREATE_TABLE_INDEX));
} catch (Exception e) {
fail();
}
@@ -140,13 +140,13 @@ public class CustomSQLiteOpenHelperTest {
assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME));
assertEquals(tablesByCreate.get(TrackPointsColumns.TABLE_NAME), tableByUpgrade.get(TrackPointsColumns.TABLE_NAME));
assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
assertEquals(tablesByCreate.get(MarkerColumns.TABLE_NAME), tableByUpgrade.get(MarkerColumns.TABLE_NAME));
// then - verify custom indices
assertEquals(3, indicesByCreate.size());
assertEquals(indicesByUpgrade.get(TracksColumns.TABLE_NAME), indicesByCreate.get(TracksColumns.TABLE_NAME));
assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
assertEquals(indicesByUpgrade.get(MarkerColumns.TABLE_NAME), indicesByCreate.get(MarkerColumns.TABLE_NAME));
}
@Test
@@ -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;
@@ -34,11 +33,12 @@ import java.util.Locale;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
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;
@@ -70,7 +70,7 @@ public abstract class AbstractTestFileTrackImporter {
static final long TRACK_POINT_ID_3 = 4;
private static final long WAYPOINT_ID_0 = 1;
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(MarkerColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
protected final Context context = ApplicationProvider.getApplicationContext();
@@ -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,10 +111,10 @@ 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);
when(contentProviderUtils.insertMarker(any())).thenReturn(WAYPOINT_ID_O_URI);
if (lastTrack) {
// Return null to not add waypoints
when(contentProviderUtils.getTrack(trackId)).thenReturn(null);
@@ -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());
}
}
}
@@ -21,10 +21,10 @@ import java.util.Arrays;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
@@ -53,11 +53,11 @@ public class ExportImportTest {
private static final String TRACK_CATEGORY = "the category";
private static final String TRACK_DESCRIPTION = "the description";
private final List<Waypoint> waypoints = new ArrayList<>();
private final List<Marker> markers = 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() {
@@ -72,19 +72,19 @@ public class ExportImportTest {
trackPoints.addAll(Arrays.asList(track.second));
for (int i = 0; i < 3; i++) {
Waypoint waypoint = new Waypoint(track.second[i].getLocation());
waypoint.setName("the waypoint " + i);
waypoint.setDescription("the waypoint description " + i);
waypoint.setCategory("the waypoint category" + i);
waypoint.setIcon("the waypoing icon" + i);
waypoint.setPhotoUrl("the photo url" + i);
waypoint.setTrackId(trackId);
contentProviderUtils.insertWaypoint(waypoint);
Marker marker = new Marker(track.second[i].getLocation());
marker.setName("the marker " + i);
marker.setDescription("the marker description " + i);
marker.setCategory("the marker category" + i);
marker.setIcon("the waypoing icon" + i);
marker.setPhotoUrl("the photo url" + i);
marker.setTrackId(trackId);
contentProviderUtils.insertMarker(marker);
waypoints.add(waypoint);
markers.add(marker);
}
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(trackId));
assertEquals(markers.size(), contentProviderUtils.getMarkerCount(trackId));
}
@After
@@ -106,12 +106,12 @@ public class ExportImportTest {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context, new Track[]{track});
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
trackExporter.writeTrack(track, outputStream);
contentProviderUtils.deleteTrack(context, trackId);
// 2. import
@@ -129,8 +129,8 @@ public class ExportImportTest {
assertEquals(track.getIcon(), importedTrack.getIcon());
assertEquals(track.getUuid(), importedTrack.getUuid());
// 2. waypoints
assertWaypoints();
// 2. markers
assertMarkers();
// 3. trackpoints
assertTrackpoints(false, false, false);
@@ -142,12 +142,12 @@ public class ExportImportTest {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context, new Track[]{track});
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
trackExporter.writeTrack(track, outputStream);
contentProviderUtils.deleteTrack(context, trackId);
// 2. import
@@ -164,8 +164,8 @@ public class ExportImportTest {
assertEquals(track.getName(), importedTrack.getName());
assertEquals(track.getIcon(), importedTrack.getIcon());
// 2. waypoints
assertWaypoints();
// 2. markers
assertMarkers();
// 3. trackpoints
assertTrackpoints(true, true, true);
@@ -178,12 +178,12 @@ public class ExportImportTest {
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context, new Track[]{track});
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
trackExporter.writeTrack(track, outputStream);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
@@ -229,12 +229,12 @@ public class ExportImportTest {
// given
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context, new Track[]{track});
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
trackExporter.writeTrack(track, outputStream);
contentProviderUtils.deleteTrack(context, trackId);
// 2. import
@@ -253,8 +253,8 @@ public class ExportImportTest {
//TODO exporting and importing a track icon is not yet supported by GpxTrackWriter.
//assertEquals(track.getIcon(), trackImported.getIcon());
// 2. waypoints
assertWaypoints();
// 2. markers
assertMarkers();
// 3. trackpoints
assertTrackpoints(false, true, true);
@@ -267,12 +267,12 @@ public class ExportImportTest {
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context, new Track[]{track});
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream);
trackExporter.writeTrack(track, outputStream);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
@@ -285,22 +285,22 @@ public class ExportImportTest {
assertNull(trackImported);
}
private void assertWaypoints() {
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(importTrackId));
private void assertMarkers() {
assertEquals(markers.size(), contentProviderUtils.getMarkerCount(importTrackId));
List<Waypoint> importedWaypoints = contentProviderUtils.getWaypoints(importTrackId);
for (int i = 0; i < waypoints.size(); i++) {
Waypoint waypoint = waypoints.get(i);
Waypoint importedWaypoint = importedWaypoints.get(i);
assertEquals(waypoint.getCategory(), importedWaypoint.getCategory());
assertEquals(waypoint.getDescription(), importedWaypoint.getDescription());
// assertEquals(waypoint.getIcon(), importedWaypoint.getIcon()); // TODO for KML
assertEquals(waypoint.getName(), importedWaypoint.getName());
assertEquals("", importedWaypoint.getPhotoUrl());
List<Marker> importedMarkers = contentProviderUtils.getMarkers(importTrackId);
for (int i = 0; i < markers.size(); i++) {
Marker marker = markers.get(i);
Marker importMarker = importedMarkers.get(i);
assertEquals(marker.getCategory(), importMarker.getCategory());
assertEquals(marker.getDescription(), importMarker.getDescription());
// assertEquals(marker.getIcon(), importMarker.getIcon()); // TODO for KML
assertEquals(marker.getName(), importMarker.getName());
assertEquals("", importMarker.getPhotoUrl());
assertEquals(waypoint.getLocation().getLatitude(), importedWaypoint.getLocation().getLatitude(), 0.001);
assertEquals(waypoint.getLocation().getLongitude(), importedWaypoint.getLocation().getLongitude(), 0.001);
assertEquals(waypoint.getLocation().getAltitude(), importedWaypoint.getLocation().getAltitude(), 0.001);
assertEquals(marker.getLocation().getLatitude(), importMarker.getLocation().getLatitude(), 0.001);
assertEquals(marker.getLocation().getLongitude(), importMarker.getLocation().getLongitude(), 0.001);
assertEquals(marker.getLocation().getAltitude(), importMarker.getLocation().getAltitude(), 0.001);
}
}
@@ -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;
@@ -43,15 +44,22 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.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);
Marker.Id waypointId = service.insertMarker(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);
Marker.Id waypointId = service.insertMarker(null, null, null, null);
// then
Assert.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());
assertNotEquals(-1L, waypointId);
Marker wpt = contentProviderUtils.getMarker(waypointId);
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();
@@ -221,20 +226,20 @@ public class TrackRecordingServiceTestLooper {
service.newTrackPoint(trackPoint, prefAccuracy);
if (i % 7 == 0) {
service.insertWaypoint(null, null, null, null);
service.insertMarker(null, null, null, null);
}
}
// 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.9", StringUtils.getSpeedParts(context, 3.31, true, true).first);
assertEquals("7.5", StringUtils.getSpeedParts(context, 3.34, 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);
}
@@ -30,7 +30,7 @@ import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.fragments.MarkerDetailFragment;
@@ -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<Marker.Id> waypointIds;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
if (markerId == -1L) {
Marker.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);
Marker waypoint = contentProviderUtils.getMarker(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.getMarkerCursor(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;
Marker currentMarker = contentProviderUtils.createMarker(cursor);
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,7 +42,8 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.FileUtils;
@@ -64,9 +65,9 @@ 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;
private Marker waypoint;
private MenuItem insertPhotoMenuItem;
private MenuItem insertGalleryImgMenuItem;
@@ -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);
Marker.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).getNextMarkerNumber(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).getMarker(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;
}
/**
@@ -306,6 +307,6 @@ public class MarkerEditActivity extends AbstractActivity {
waypoint.setDescription(waypointDescription.getText().toString());
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
new ContentProviderUtils(this).updateWaypoint(this, waypoint);
new ContentProviderUtils(this).updateMarker(this, waypoint);
}
}
@@ -32,11 +32,11 @@ 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.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
@@ -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,31 +112,31 @@ 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 Marker.Id(id));
startActivity(intent);
});
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@Override
public void bindView(View view, Context context, Cursor cursor) {
int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME);
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(WaypointsColumns.DESCRIPTION);
int photoUrlIndex = cursor.getColumnIndex(WaypointsColumns.PHOTOURL);
int latitudeIndex = cursor.getColumnIndex(WaypointsColumns.LATITUDE);
int longitudeIndex = cursor.getColumnIndex(WaypointsColumns.LONGITUDE);
int nameIndex = cursor.getColumnIndex(MarkerColumns.NAME);
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
int categoryIndex = cursor.getColumnIndex(MarkerColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(MarkerColumns.DESCRIPTION);
int photoUrlIndex = cursor.getColumnIndex(MarkerColumns.PHOTOURL);
int latitudeIndex = cursor.getColumnIndex(MarkerColumns.LATITUDE);
int longitudeIndex = cursor.getColumnIndex(MarkerColumns.LONGITUDE);
int iconId = MarkerUtils.ICON_ID;
String name = cursor.getString(nameIndex);
@@ -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.getMarkersLoader(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) {
Marker.Id[] waypointIds = new Marker.Id[longWaypointIds.length];
for (int i = 0; i < longWaypointIds.length; i++) {
waypointIds[i] = new Marker.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.getMarker(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++) {
@@ -41,8 +41,8 @@ import java.util.SortedSet;
import de.dennisguse.opentracks.content.SearchEngine;
import de.dennisguse.opentracks.content.SearchEngine.ScoredResult;
import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.data.Marker;
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.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
@@ -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,8 +203,8 @@ 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);
Long markerId = (Long) item.get(MARKER_ID_FIELD);
Track.Id trackId = (Track.Id) item.get(TRACK_ID_FIELD);
Marker.Id markerId = (Marker.Id) item.get(MARKER_ID_FIELD);
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class);
if (markerId != null) {
intent = intent.putExtra(TrackRecordedActivity.EXTRA_MARKER_ID, markerId);
@@ -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);
Marker.Id waypointId = (Marker.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);
@@ -355,7 +355,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
if (result.track != null) {
output.add(prepareTrackForDisplay(result.track));
} else {
output.add(prepareMarkerForDisplay(result.waypoint));
output.add(prepareMarkerForDisplay(result.marker));
}
}
return output;
@@ -367,13 +367,13 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
* @param waypoint the marker
* @return the result map
*/
private Map<String, Object> prepareMarkerForDisplay(Waypoint waypoint) {
private Map<String, Object> prepareMarkerForDisplay(Marker waypoint) {
Map<String, Object> resultMap = new HashMap<>();
//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();
@@ -422,7 +422,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
resultMap.put(NAME_FIELD, track.getName());
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
resultMap.put(TOTAL_DISTANCE_FIELD, StringUtils.formatDistance(this, trackStatistics.getTotalDistance(), metricUnits));
resultMap.put(MARKER_COUNT_FIELD, contentProviderUtils.getWaypointCount(track.getId()));
resultMap.put(MARKER_COUNT_FIELD, contentProviderUtils.getMarkerCount(track.getId()));
resultMap.put(START_TIME_FIELD, trackStatistics.getStartTime_ms());
resultMap.put(CATEGORY_FIELD, category);
resultMap.put(DESCRIPTION_FIELD, track.getDescription());
@@ -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,14 +262,14 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION);
long trackId = cursor.getLong(idIndex);
boolean isRecording = trackId == recordingTrackId;
Track.Id trackId = new Track.Id(cursor.getLong(idIndex));
boolean isRecording = trackId.equals(recordingTrackId);
String icon = cursor.getString(iconIndex);
int iconId = TrackIconUtils.getIconDrawable(icon);
String name = cursor.getString(nameIndex);
String totalTime = StringUtils.formatElapsedTime(cursor.getLong(totalTimeIndex));
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, cursor.getDouble(totalDistanceIndex), metricUnits);
int markerCount = contentProviderUtils.getWaypointCount(trackId);
int markerCount = contentProviderUtils.getMarkerCount(trackId);
long startTime = cursor.getLong(startTimeIndex);
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex);
@@ -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,18 +24,20 @@ 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;
import com.google.android.material.tabs.TabLayout;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.data.Marker;
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.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) {
Marker.Id waypointId = intent.getParcelableExtra(EXTRA_MARKER_ID);
if (waypointId != null) {
// Use the trackId from the marker
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId);
Marker waypoint = contentProviderUtils.getMarker(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,11 +61,14 @@ 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;
// Intervals recording fragment needs Track.Id when the activity creates it.
private OnTrackIdListener intervalsListener;
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
@@ -78,9 +82,12 @@ 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();
if (intervalsListener != null) {
intervalsListener.onTrackId(trackId);
}
} else {
// trackId is initialized -> resumes the track.
service.resumeTrack(trackId);
@@ -161,13 +168,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 +188,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 +256,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 +274,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 +323,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 +380,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@Override
public int getCount() {
return 3;
return 4;
}
@NonNull
@@ -379,10 +388,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,12 +406,22 @@ 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);
}
}
}
public interface OnTrackIdListener {
void onTrackId(Track.Id trackId);
}
public void setTrackIdListener(OnTrackIdListener listener) {
this.intervalsListener = listener;
}
}
@@ -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;
}
}
@@ -42,7 +42,7 @@ import java.util.List;
import de.dennisguse.opentracks.MarkerDetailActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.stats.ExtremityMonitor;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
@@ -87,7 +87,7 @@ public class ChartView extends View {
private final ChartValueSeries paceSeries;
private final List<ChartPoint> chartPoints = new ArrayList<>();
private final List<Waypoint> waypoints = new ArrayList<>();
private final List<Marker> waypoints = new ArrayList<>();
private final ExtremityMonitor xExtremityMonitor = new ExtremityMonitor();
private final Paint axisPaint;
private final Paint xAxisMarkerPaint;
@@ -150,9 +150,9 @@ public class ChartView extends View {
// Check if the y event is within markerHeight of the marker center
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2f) < markerHeight) {
int minDistance = Integer.MAX_VALUE;
Waypoint nearestWaypoint = null;
Marker nearestWaypoint = null;
synchronized (waypoints) {
for (Waypoint waypoint : waypoints) {
for (Marker waypoint : waypoints) {
int distance = Math.abs(getX(getWaypointXValue(waypoint)) - (int) event.getX() - getScrollX());
if (distance < minDistance) {
minDistance = distance;
@@ -434,7 +434,7 @@ public class ChartView extends View {
scrollTo(0, 0);
}
public void addWaypoint(Waypoint waypoint) {
public void addWaypoint(Marker waypoint) {
synchronized (waypoints) {
waypoints.add(waypoint);
}
@@ -594,7 +594,7 @@ public class ChartView extends View {
*/
private void drawWaypoints(Canvas canvas) {
synchronized (waypoints) {
for (Waypoint waypoint : waypoints) {
for (Marker waypoint : waypoints) {
double xValue = getWaypointXValue(waypoint);
if (xValue > maxX) {
continue;
@@ -980,7 +980,7 @@ public class ChartView extends View {
*
* @param waypoint the waypoint
*/
private double getWaypointXValue(Waypoint waypoint) {
private double getWaypointXValue(Marker waypoint) {
if (chartByDistance) {
double lenghtInKm = waypoint.getLength() * UnitConversions.M_TO_KM;
return metricUnits ? lenghtInKm : lenghtInKm * UnitConversions.KM_TO_MI;
@@ -28,7 +28,7 @@ import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* Generates descriptions for tracks and waypoints.
* Generates descriptions for tracks and markers.
*
* @author Jimmy Shih
*/
@@ -28,17 +28,17 @@ import java.util.Locale;
import java.util.SortedSet;
import java.util.TreeSet;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* Engine for searching for tracks and waypoints by text.
* Engine for searching for tracks and markers by text.
*
* @author Rodrigo Damazio
*/
@@ -53,12 +53,12 @@ public class SearchEngine {
TracksColumns.CATEGORY + " LIKE ?";
/**
* WHERE query to get waypoints by name.
* WHERE query to get markers by name.
*/
private static final String WAYPOINT_SELECTION_QUERY =
WaypointsColumns.NAME + " LIKE ? OR " +
WaypointsColumns.DESCRIPTION + " LIKE ? OR " +
WaypointsColumns.CATEGORY + " LIKE ?";
private static final String MARKER_SELECTION_QUERY =
MarkerColumns.NAME + " LIKE ? OR " +
MarkerColumns.DESCRIPTION + " LIKE ? OR " +
MarkerColumns.CATEGORY + " LIKE ?";
/**
* Order of track results.
@@ -66,9 +66,9 @@ public class SearchEngine {
private static final String TRACK_SELECTION_ORDER = TracksColumns._ID + " DESC LIMIT 1000";
/**
* Order of waypoint results.
* Order of marker results.
*/
private static final String WAYPOINT_SELECTION_ORDER = WaypointsColumns._ID + " DESC";
private static final String MARKER_SELECTION_ORDER = MarkerColumns._ID + " DESC";
/**
* How much we promote a match in the track category.
@@ -86,9 +86,9 @@ public class SearchEngine {
private static final double TRACK_NAME_PROMOTION = 16.0;
/**
* How much we promote a waypoint result if it's in the currently-selected track.
* How much we promote a marker result if it's in the currently-selected track.
*/
private static final double CURRENT_TRACK_WAYPOINT_PROMOTION = 2.0;
private static final double CURRENT_TRACK_MARKER_PROMOTION = 2.0;
/**
* How much we promote a track result if it's the currently-selected track.
@@ -96,9 +96,9 @@ public class SearchEngine {
private static final double CURRENT_TRACK_DEMOTION = 0.5;
/**
* Maximum number of waypoints which will be retrieved and scored.
* Maximum number of markers which will be retrieved and scored.
*/
private static final int MAX_SCORED_WAYPOINTS = 100;
private static final int MAX_SCORED_MARKERS = 100;
/**
* Oldest timestamp for which we rank based on time (2000-01-01 00:00:00.000)
@@ -115,16 +115,16 @@ public class SearchEngine {
return scoreDiff;
}
// Make tracks come before waypoints.
if (r1.waypoint != null && r2.track != null) {
// Make tracks come before markers.
if (r1.marker != null && r2.track != null) {
return 1;
} else if (r1.track != null && r2.waypoint != null) {
} else if (r1.track != null && r2.marker != null) {
return -1;
}
// 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.marker.getId().getId();
long id2 = r2.track != null ? r2.track.getId().getId() : r2.marker.getId().getId();
long idDiff = id2 - id1;
return Long.signum(idDiff);
};
@@ -153,7 +153,7 @@ public class SearchEngine {
TreeSet<ScoredResult> scoredResults = new TreeSet<>(SCORED_RESULT_COMPARATOR);
scoreTrackResults(retrieveTracks(query), query, scoredResults);
scoreWaypointResults(retrieveWaypoints(query), query, scoredResults);
scoreMarkersResults(retrieveMarkers(query), query, scoredResults);
return scoredResults;
}
@@ -186,33 +186,33 @@ public class SearchEngine {
}
/**
* Retrieves waypoints matching the given query from the database.
* Retrieves markers matching the given query from the database.
*
* @param query the query to retrieve for
*/
private List<Waypoint> retrieveWaypoints(SearchQuery query) {
ArrayList<Waypoint> waypoints = new ArrayList<>();
private List<Marker> retrieveMarkers(SearchQuery query) {
ArrayList<Marker> markers = new ArrayList<>();
String queryLikeSelection2 = "%" + query.textQuery + "%";
String[] waypointSelectionArgs = new String[]{
String[] markersSelectionArgs = new String[]{
queryLikeSelection2,
queryLikeSelection2,
queryLikeSelection2
};
try (Cursor cursor = providerUtils.getWaypointCursor(WAYPOINT_SELECTION_QUERY, waypointSelectionArgs, WAYPOINT_SELECTION_ORDER, MAX_SCORED_WAYPOINTS)) {
try (Cursor cursor = providerUtils.getMarkerCursor(MARKER_SELECTION_QUERY, markersSelectionArgs, MARKER_SELECTION_ORDER, MAX_SCORED_MARKERS)) {
if (cursor != null) {
waypoints.ensureCapacity(cursor.getCount());
markers.ensureCapacity(cursor.getCount());
while (cursor.moveToNext()) {
Waypoint waypoint = providerUtils.createWaypoint(cursor);
if (LocationUtils.isValidLocation(waypoint.getLocation())) {
waypoints.add(waypoint);
Marker marker = providerUtils.createMarker(cursor);
if (LocationUtils.isValidLocation(marker.getLocation())) {
markers.add(marker);
}
}
}
}
return waypoints;
return markers;
}
/**
@@ -258,40 +258,40 @@ public class SearchEngine {
}
/**
* Scores a collection of waypoint results.
* Scores a collection of marker results.
*
* @param waypoints the results to score
* @param query the query to score for
* @param output the collection to fill with scored results
* @param markers the results to score
* @param query the query to score for
* @param output the collection to fill with scored results
*/
private void scoreWaypointResults(Collection<Waypoint> waypoints, SearchQuery query, Collection<ScoredResult> output) {
for (Waypoint waypoint : waypoints) {
private void scoreMarkersResults(Collection<Marker> markers, SearchQuery query, Collection<ScoredResult> output) {
for (Marker marker : markers) {
// Calculate the score.
double score = scoreWaypointResult(query, waypoint);
double score = scoreMarkerResult(query, marker);
// Add to the output.
output.add(new ScoredResult(waypoint, score));
output.add(new ScoredResult(marker, score));
}
}
/**
* Scores a single waypoint result.
* Scores a single marker result.
*
* @param query the query to score for
* @param waypoint the results to score
* @return the score for the waypoint
* @param query the query to score for
* @param marker the results to score
* @return the score for the marker
*/
private double scoreWaypointResult(SearchQuery query, Waypoint waypoint) {
private double scoreMarkerResult(SearchQuery query, Marker marker) {
double score = 1.0;
Location location = waypoint.getLocation();
score *= getTitleBoost(query, waypoint.getName(), waypoint.getDescription(), waypoint.getCategory());
Location location = marker.getLocation();
score *= getTitleBoost(query, marker.getName(), marker.getDescription(), marker.getCategory());
score *= getDistanceBoost(query, location.getLatitude(), location.getLongitude());
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) {
score *= CURRENT_TRACK_WAYPOINT_PROMOTION;
// Score markers in the currently-selected track higher (searching inside the current track).
if (marker.getTrackId() != null && marker.getTrackId().equals(query.currentTrackId)) {
score *= CURRENT_TRACK_MARKER_PROMOTION;
}
return score;
@@ -301,9 +301,9 @@ public class SearchEngine {
* Calculates the boosting of the score due to the field(s) in which the match occured.
*
* @param query the query to boost for
* @param name the name of the track or waypoint
* @param description the description of the track or waypoint
* @param category the category of the track or waypoint
* @param name the name of the track or marker
* @param description the description of the track or marker
* @param category the category of the track or marker
* @return the total boost to be applied to the result
*/
private double getTitleBoost(SearchQuery query, String name, String description, String category) {
@@ -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;
@@ -398,18 +398,18 @@ public class SearchEngine {
*/
public static class ScoredResult {
public final Track track;
public final Waypoint waypoint;
public final Marker marker;
final double score;
ScoredResult(Track track, double score) {
this.track = track;
this.waypoint = null;
this.marker = null;
this.score = score;
}
ScoredResult(Waypoint waypoint, double score) {
ScoredResult(Marker marker, double score) {
this.track = null;
this.waypoint = waypoint;
this.marker = marker;
this.score = score;
}
@@ -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() + ", ") : "")
+ (marker != null ? ("wptId=" + marker.getId().getId() + ", ") : "")
+ "score=" + score + "]";
}
}
@@ -25,18 +25,19 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.Collections;
import java.util.Set;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.LocationUtils;
@@ -57,14 +58,14 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
* We may display more than this number of points.
*/
@Deprecated
private static final int TARGET_DISPLAYED_TRACK_POINTS = 5000;
private static final int TARGET_DISPLAYED_TRACKPOINTS = 5000;
/**
* Maximum number of waypoints to displayed.
* Maximum number of markers to displayed.
*/
@VisibleForTesting
@Deprecated
private static final int MAX_DISPLAYED_WAYPOINTS = 128;
private static final int MAX_DISPLAYED_MARKERS = 128;
private static final String TAG = TrackDataHub.class.getSimpleName();
@@ -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
@@ -89,11 +90,11 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
// Registered listeners
private ContentObserver tracksTableObserver;
private ContentObserver waypointsTableObserver;
private ContentObserver markersTableObserver;
private ContentObserver trackPointsTableObserver;
public TrackDataHub(Context context) {
this(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACK_POINTS);
this(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACKPOINTS);
}
@VisibleForTesting
@@ -125,13 +126,13 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
};
contentResolver.registerContentObserver(TracksColumns.CONTENT_URI, false, tracksTableObserver);
waypointsTableObserver = new ContentObserver(handler) {
markersTableObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
notifyWaypointsTableUpdate(trackDataManager.getListenerWaypoints());
notifyMarkersTableUpdate(trackDataManager.getListenerMarkers());
}
};
contentResolver.registerContentObserver(WaypointsColumns.CONTENT_URI, false, waypointsTableObserver);
contentResolver.registerContentObserver(MarkerColumns.CONTENT_URI, false, markersTableObserver);
trackPointsTableObserver = new ContentObserver(handler) {
@Override
@@ -164,7 +165,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
//Unregister listeners
ContentResolver contentResolver = context.getContentResolver();
contentResolver.unregisterContentObserver(tracksTableObserver);
contentResolver.unregisterContentObserver(waypointsTableObserver);
contentResolver.unregisterContentObserver(markersTableObserver);
contentResolver.unregisterContentObserver(trackPointsTableObserver);
if (handlerThread != null) {
@@ -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;
@@ -190,9 +191,9 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
*
* @param trackDataListener the track data listener
*/
public void registerTrackDataListener(final TrackDataListener trackDataListener, final boolean tracksTable, final boolean waypointsTable, final boolean trackPointsTable_SampleIn, final boolean trackPointsTable_SampleOut) {
public void registerTrackDataListener(final TrackDataListener trackDataListener, final boolean tracksTable, final boolean markersTable, final boolean trackPointsTable_SampleIn, final boolean trackPointsTable_SampleOut) {
runInHandlerThread(() -> {
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, waypointsTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, markersTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
if (started) {
loadDataForListener(trackDataListener);
}
@@ -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
@@ -249,7 +250,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
listener.clearTrackPoints();
}
notifyTrackPointsTableUpdate(true, trackDataManager.getListenerTrackPoints_SampledIn(), trackDataManager.getListenerTrackPoints_SampledOut());
notifyWaypointsTableUpdate(trackDataManager.getListenerWaypoints());
notifyMarkersTableUpdate(trackDataManager.getListenerMarkers());
}
/**
@@ -276,8 +277,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
notifyTrackPointsTableUpdate(isOnlyListener, trackDataListeners, sampledOutListeners);
}
if (trackDataManager.listensForWaypoints(trackDataListener)) {
notifyWaypointsTableUpdate(trackDataListeners);
if (trackDataManager.listensForMarkers(trackDataListener)) {
notifyMarkersTableUpdate(trackDataListeners);
}
}
@@ -297,36 +298,36 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
}
/**
* Notifies waypoint table update.
* Currently, reloads all the waypoints up to {@link #MAX_DISPLAYED_WAYPOINTS}. To be run in the {@link #handler} thread.
* Notifies marker table update.
* Currently, reloads all the markers up to {@link #MAX_DISPLAYED_MARKERS}. To be run in the {@link #handler} thread.
*
* @param trackDataListeners the track data listeners to notify
*/
private void notifyWaypointsTableUpdate(Set<TrackDataListener> trackDataListeners) {
private void notifyMarkersTableUpdate(Set<TrackDataListener> trackDataListeners) {
if (trackDataListeners.isEmpty()) {
return;
}
for (TrackDataListener trackDataListener : trackDataListeners) {
trackDataListener.clearWaypoints();
trackDataListener.clearMarkers();
}
try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) {
try (Cursor cursor = contentProviderUtils.getMarkerCursor(selectedTrackId, null, MAX_DISPLAYED_MARKERS)) {
if (cursor != null && cursor.moveToFirst()) {
do {
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
if (!LocationUtils.isValidLocation(waypoint.getLocation())) {
Marker marker = contentProviderUtils.createMarker(cursor);
if (!LocationUtils.isValidLocation(marker.getLocation())) {
continue;
}
for (TrackDataListener trackDataListener : trackDataListeners) {
trackDataListener.onNewWaypoint(waypoint);
trackDataListener.onNewMarker(marker);
}
} while (cursor.moveToNext());
}
}
for (TrackDataListener trackDataListener : trackDataListeners) {
trackDataListener.onNewWaypointsDone();
trackDataListener.onNewMarkersDone();
}
}
@@ -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,9 @@
package de.dennisguse.opentracks.content;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
/**
* Listener for track data changes.
@@ -60,20 +60,20 @@ public interface TrackDataListener {
void onNewTrackPointsDone();
/**
* Called to clear previously sent waypoints.
* Called to clear previously sent markers.
*/
void clearWaypoints();
void clearMarkers();
/**
* Called when a new waypoint is read.
* Called when a new marker is read.
*
* @param waypoint the waypoint
* @param marker the marker
*/
void onNewWaypoint(Waypoint waypoint);
void onNewMarker(Marker marker);
/**
* Called when finish sending new waypoints.
* This gets called after every batch of calls to {@link #clearWaypoints()} and {@link #onNewWaypoint(Waypoint)}.
* Called when finish sending new markers.
* This gets called after every batch of calls to {@link #clearMarkers()} and {@link #onNewMarker(Marker)}.
*/
void onNewWaypointsDone();
void onNewMarkersDone();
}
@@ -29,32 +29,32 @@ class TrackDataManager {
private static final String TAG = TrackDataManager.class.getSimpleName();
private final Set<TrackDataListener> dataListenerTracks = new HashSet<>();
private final Set<TrackDataListener> dataListenerWaypoints = new HashSet<>();
private final Set<TrackDataListener> dataListenerMarkers = new HashSet<>();
private final Set<TrackDataListener> dataListenerTrackPoints_SampledIn = new HashSet<>();
private final Set<TrackDataListener> dataListenerTrackPoints_SampledOut = new HashSet<>();
void registerTrackDataListener(final TrackDataListener trackDataListener, boolean tracksTable, boolean waypointsTable, boolean trackPointsTable_SampleIn, boolean trackPointsTable_SampleOut) {
void registerTrackDataListener(final TrackDataListener trackDataListener, boolean tracksTable, boolean markersTable, boolean trackPointsTable_SampleIn, boolean trackPointsTable_SampleOut) {
if (tracksTable) dataListenerTracks.add(trackDataListener);
if (waypointsTable) dataListenerWaypoints.add(trackDataListener);
if (markersTable) dataListenerMarkers.add(trackDataListener);
if (trackPointsTable_SampleIn) dataListenerTrackPoints_SampledIn.add(trackDataListener);
if (trackPointsTable_SampleOut) dataListenerTrackPoints_SampledOut.add(trackDataListener);
}
void unregisterTrackDataListener(TrackDataListener trackDataListener) {
dataListenerTracks.add(trackDataListener);
dataListenerWaypoints.add(trackDataListener);
dataListenerMarkers.add(trackDataListener);
dataListenerTrackPoints_SampledIn.add(trackDataListener);
dataListenerTrackPoints_SampledOut.add(trackDataListener);
}
boolean hasListeners() {
return dataListenerTracks.size() + dataListenerWaypoints.size() + dataListenerTrackPoints_SampledIn.size() + dataListenerTrackPoints_SampledOut.size() > 0;
return dataListenerTracks.size() + dataListenerMarkers.size() + dataListenerTrackPoints_SampledIn.size() + dataListenerTrackPoints_SampledOut.size() > 0;
}
int getNumberOfListeners() {
Set<TrackDataListener> listener = new HashSet<>();
listener.addAll(dataListenerTracks);
listener.addAll(dataListenerWaypoints);
listener.addAll(dataListenerMarkers);
listener.addAll(dataListenerTrackPoints_SampledIn);
listener.addAll(dataListenerTrackPoints_SampledOut);
@@ -65,8 +65,8 @@ class TrackDataManager {
return dataListenerTracks.contains(listener);
}
boolean listensForWaypoints(TrackDataListener listener) {
return dataListenerWaypoints.contains(listener);
boolean listensForMarkers(TrackDataListener listener) {
return dataListenerMarkers.contains(listener);
}
boolean listensForTrackPoints_SampledIn(TrackDataListener listener) {
@@ -81,8 +81,8 @@ class TrackDataManager {
return dataListenerTracks;
}
Set<TrackDataListener> getListenerWaypoints() {
return dataListenerWaypoints;
Set<TrackDataListener> getListenerMarkers() {
return dataListenerMarkers;
}
Set<TrackDataListener> getListenerTrackPoints_SampledIn() {
@@ -18,24 +18,29 @@ 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.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public final class Waypoint {
public final class Marker {
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;
@@ -43,15 +48,15 @@ public final class Waypoint {
private String photoUrl = "";
@VisibleForTesting
public Waypoint(@NonNull TrackPoint trackPoint) {
public Marker(@NonNull TrackPoint trackPoint) {
this.location = trackPoint.getLocation();
}
public Waypoint(@NonNull Location location) {
public Marker(@NonNull Location location) {
this.location = location;
}
public Waypoint(String name, String description, String category, String icon, long trackId, double length, long duration, @NonNull Location location, String photoUrl) {
public Marker(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 +68,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 +112,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 +156,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];
}
};
}
}
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
*
* @author Leif Hendrik Wilden
*/
public interface WaypointsColumns extends BaseColumns {
public interface MarkerColumns extends BaseColumns {
String TABLE_NAME = "waypoints";
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
@@ -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];
}
};
}
}
@@ -59,10 +59,12 @@ public class TrackPoint {
location.setTime(time);
}
@Deprecated //See #316
public static TrackPoint createPause() {
return createPauseWithTime(System.currentTimeMillis());
}
@Deprecated //See #316
public static TrackPoint createPauseWithTime(long time) {
Location pause = new Location(LocationManager.GPS_PROVIDER);
pause.setLongitude(0);
@@ -71,10 +73,12 @@ public class TrackPoint {
return new TrackPoint(pause);
}
@Deprecated //See #316
public static TrackPoint createResume() {
return createResumeWithTime(System.currentTimeMillis());
}
@Deprecated //See #316
public static TrackPoint createResumeWithTime(long time) {
Location resume = new Location(LocationManager.GPS_PROVIDER);
resume.setLongitude(0);
@@ -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;
@@ -36,14 +40,15 @@ import java.util.UUID;
import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.android.ContentResolverWrapper;
import de.dennisguse.opentracks.android.IContentResolver;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.TrackUtils;
import de.dennisguse.opentracks.util.UUIDUtils;
/**
@@ -64,8 +69,8 @@ public class ContentProviderUtils {
// The base URI for the app's content provider.
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
// Maximum number of waypoints that will be loaded at one time.
public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000;
// Maximum number of markers that will be loaded at one time.
public static final int MAX_LOADED_MARKERS = 10000;
private static final String ID_SEPARATOR = ",";
private final IContentResolver contentResolver;
@@ -84,13 +89,13 @@ public class ContentProviderUtils {
}
/**
* Clears a track: removes waypoints and trackPoints.
* Clears a track: removes markers and trackPoints.
* Only keeps the track id.
*
* @param trackId the track id
*/
public void clearTrack(long trackId) {
deleteTrackPointsAndWaypoints(trackId);
public void clearTrack(Track.Id trackId) {
deleteTrackPointsAndMarkers(trackId);
Track track = new Track();
track.setId(trackId);
updateTrack(track);
@@ -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)));
@@ -171,7 +176,7 @@ public class ContentProviderUtils {
@VisibleForTesting
public void deleteAllTracks(Context context) {
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, null, null);
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
contentResolver.delete(MarkerColumns.CONTENT_URI, null, null);
// Delete tracks last since it triggers a database vaccum call
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
@@ -184,30 +189,28 @@ public class ContentProviderUtils {
*
* @param trackId the track id
*/
public void deleteTrack(Context context, long trackId) {
deleteTrackPointsAndWaypoints(trackId);
public void deleteTrack(Context context, Track.Id trackId) {
if (trackId == null) {
return;
}
deleteTrackPointsAndMarkers(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())});
}
/**
* Deletes trackPoints and waypoints of a track.
*
* @param trackId the track id
*/
private void deleteTrackPointsAndWaypoints(long trackId) {
String[] selectionArgs = new String[]{Long.toString(trackId)};
private void deleteTrackPointsAndMarkers(@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);
contentResolver.delete(MarkerColumns.CONTENT_URI, MarkerColumns.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 +223,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 +247,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 +302,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 +310,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());
@@ -319,28 +333,22 @@ public class ContentProviderUtils {
return values;
}
/**
* Creates a waypoint from a cursor.
*
* @param cursor the cursor pointing to the waypoint
*/
public Waypoint createWaypoint(Cursor cursor) {
int idIndex = cursor.getColumnIndexOrThrow(WaypointsColumns._ID);
int nameIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.NAME);
int descriptionIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DESCRIPTION);
int categoryIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.CATEGORY);
int iconIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ICON);
int trackIdIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TRACKID);
int lengthIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LENGTH);
int durationIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.DURATION);
int longitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LONGITUDE);
int latitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.LATITUDE);
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
int altitudeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ALTITUDE);
int accuracyIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY);
int bearingIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING);
int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL);
public Marker createMarker(Cursor cursor) {
int idIndex = cursor.getColumnIndexOrThrow(MarkerColumns._ID);
int nameIndex = cursor.getColumnIndexOrThrow(MarkerColumns.NAME);
int descriptionIndex = cursor.getColumnIndexOrThrow(MarkerColumns.DESCRIPTION);
int categoryIndex = cursor.getColumnIndexOrThrow(MarkerColumns.CATEGORY);
int iconIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ICON);
int trackIdIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TRACKID);
int lengthIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LENGTH);
int durationIndex = cursor.getColumnIndexOrThrow(MarkerColumns.DURATION);
int longitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LONGITUDE);
int latitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LATITUDE);
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
int altitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ALTITUDE);
int accuracyIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ACCURACY);
int bearingIndex = cursor.getColumnIndexOrThrow(MarkerColumns.BEARING);
int photoUrlIndex = cursor.getColumnIndexOrThrow(MarkerColumns.PHOTOURL);
Location location = new Location("");
if (!cursor.isNull(longitudeIndex) && !cursor.isNull(latitudeIndex)) {
@@ -360,59 +368,56 @@ public class ContentProviderUtils {
location.setBearing(cursor.getFloat(bearingIndex));
}
Waypoint waypoint = new Waypoint(location);
Marker marker = new Marker(location);
if (!cursor.isNull(idIndex)) {
waypoint.setId(cursor.getLong(idIndex));
marker.setId(new Marker.Id(cursor.getLong(idIndex)));
}
if (!cursor.isNull(nameIndex)) {
waypoint.setName(cursor.getString(nameIndex));
marker.setName(cursor.getString(nameIndex));
}
if (!cursor.isNull(descriptionIndex)) {
waypoint.setDescription(cursor.getString(descriptionIndex));
marker.setDescription(cursor.getString(descriptionIndex));
}
if (!cursor.isNull(categoryIndex)) {
waypoint.setCategory(cursor.getString(categoryIndex));
marker.setCategory(cursor.getString(categoryIndex));
}
if (!cursor.isNull(iconIndex)) {
waypoint.setIcon(cursor.getString(iconIndex));
marker.setIcon(cursor.getString(iconIndex));
}
if (!cursor.isNull(trackIdIndex)) {
waypoint.setTrackId(cursor.getLong(trackIdIndex));
marker.setTrackId(new Track.Id(cursor.getLong(trackIdIndex)));
}
if (!cursor.isNull(lengthIndex)) {
waypoint.setLength(cursor.getFloat(lengthIndex));
marker.setLength(cursor.getFloat(lengthIndex));
}
if (!cursor.isNull(durationIndex)) {
waypoint.setDuration(cursor.getLong(durationIndex));
marker.setDuration(cursor.getLong(durationIndex));
}
if (!cursor.isNull(photoUrlIndex)) {
waypoint.setPhotoUrl(cursor.getString(photoUrlIndex));
marker.setPhotoUrl(cursor.getString(photoUrlIndex));
}
return waypoint;
return marker;
}
public void deleteWaypoint(Context context, long waypointId) {
final Waypoint waypoint = getWaypoint(waypointId);
deleteWaypointPhoto(context, waypoint);
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)});
public void deleteMarker(Context context, Marker.Id markerId) {
final Marker marker = getMarker(markerId);
deleteMarkerPhoto(context, marker);
contentResolver.delete(MarkerColumns.CONTENT_URI, MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.getId())});
}
/**
* Gets the next waypoint number.
*
* @param trackId the track id
* @return -1 if not able to get the next waypoint number.
* @return -1 if not able to get the next marker number.
*/
public int getNextWaypointNumber(long trackId) {
if (trackId < 0) {
public int getNextMarkerNumber(Track.Id trackId) {
if (!trackId.isValid()) {
return -1;
}
String[] projection = {WaypointsColumns._ID};
String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)};
try (Cursor cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1)) {
String[] projection = {MarkerColumns._ID};
String selection = MarkerColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getMarkerCursor(projection, selection, selectionArgs, MarkerColumns._ID, -1)) {
if (cursor != null) {
return cursor.getCount();
}
@@ -420,84 +425,94 @@ public class ContentProviderUtils {
return -1;
}
public Waypoint getWaypoint(long waypointId) {
if (waypointId < 0) {
public Marker getMarker(Marker.Id markerId) {
if (!markerId.isValid()) {
return null;
}
try (Cursor cursor = getWaypointCursor(null, WaypointsColumns._ID + "=?",
new String[]{Long.toString(waypointId)}, WaypointsColumns._ID, 1)) {
try (Cursor cursor = getMarkerCursor(null, MarkerColumns._ID + "=?",
new String[]{Long.toString(markerId.getId())}, MarkerColumns._ID, 1)) {
if (cursor != null && cursor.moveToFirst()) {
return createWaypoint(cursor);
return createMarker(cursor);
}
}
return null;
}
/**
* Gets a waypoint cursor.
* he caller owns the returned cursor and is responsible for closing it.
* The caller owns the returned cursor and is responsible for closing it.
*
* @param selection the selection. Can be null
* @param selectionArgs the selection arguments. Can be null
* @param sortOrder the sort order. Can be null
* @param maxWaypoints the maximum number of waypoints to return. -1 for no limit
* @param maxCount the maximum number of markers to return. -1 for no limit
*/
public Cursor getWaypointCursor(String selection, String[] selectionArgs, String sortOrder, int maxWaypoints) {
return getWaypointCursor(null, selection, selectionArgs, sortOrder, maxWaypoints);
public Cursor getMarkerCursor(String selection, String[] selectionArgs, String sortOrder, int maxCount) {
return getMarkerCursor(null, selection, selectionArgs, sortOrder, maxCount);
}
/**
* Gets a waypoint cursor for a track.
* 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 maxWaypoints the maximum number of waypoints to return. -1 for no limit
* @param trackId the track id
* @param minMarkerId the minimum marker id. null to ignore
* @param maxCount the maximum number of markers to return. -1 for no limit
*/
public Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints) {
if (trackId < 0) {
public Cursor getMarkerCursor(Track.Id trackId, @Nullable Marker.Id minMarkerId, int maxCount) {
if (!trackId.isValid()) {
return null;
}
String selection;
String[] selectionArgs;
if (minWaypointId >= 0) {
selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId), Long.toString(minWaypointId)};
if (minMarkerId != null && minMarkerId.isValid()) {
selection = MarkerColumns.TRACKID + "=? AND " + MarkerColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minMarkerId.getId())};
} else {
selection = WaypointsColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId)};
selection = MarkerColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId.getId())};
}
return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints);
return getMarkerCursor(null, selection, selectionArgs, MarkerColumns._ID, maxCount);
}
@Deprecated //TODO Move to test package
@VisibleForTesting
public List<Waypoint> getWaypoints(long trackId) {
ArrayList<Waypoint> waypoints = new ArrayList<>();
try (Cursor cursor = getWaypointCursor(trackId, -1L, -1)) {
public List<Marker> getMarkers(Track.Id trackId) {
ArrayList<Marker> markers = new ArrayList<>();
try (Cursor cursor = getMarkerCursor(trackId, null, -1)) {
if (cursor.moveToFirst()) {
do {
waypoints.add(createWaypoint(cursor));
markers.add(createMarker(cursor));
} while (cursor.moveToNext());
}
}
return waypoints;
return markers;
}
/**
* Gets the number of waypoints for a track.
*
* @param trackId the track id
*/
public int getWaypointCount(long trackId) {
if (trackId < 0) {
public static Loader<Cursor> getMarkersLoader(Context context, @Nullable Track.Id trackId) {
final String[] PROJECTION = new String[]{MarkerColumns._ID,
MarkerColumns.NAME, MarkerColumns.DESCRIPTION, MarkerColumns.CATEGORY,
MarkerColumns.TIME, MarkerColumns.PHOTOURL,
MarkerColumns.LATITUDE, MarkerColumns.LONGITUDE};
if (trackId != null) {
return new CursorLoader(context, MarkerColumns.CONTENT_URI, PROJECTION,
MarkerColumns.TRACKID + "=?",
new String[]{String.valueOf(trackId.getId())}, null);
} else {
return new CursorLoader(context, MarkerColumns.CONTENT_URI, PROJECTION, null, null, null);
}
}
@Deprecated //TODO Expose information via Cursor
public int getMarkerCount(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)};
try (Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID)) {
String selection = MarkerColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = contentResolver.query(MarkerColumns.CONTENT_URI, projection, selection, selectionArgs, MarkerColumns._ID)) {
if (cursor == null) {
return 0;
}
@@ -507,26 +522,17 @@ public class ContentProviderUtils {
}
/**
* Inserts a waypoint.
*
* @param waypoint the waypoint
* @return the content provider URI of the inserted waypoint.
* @return the content provider URI of the inserted marker.
*/
public Uri insertWaypoint(Waypoint waypoint) {
waypoint.setId(-1L);
return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint));
public Uri insertMarker(@NonNull Marker marker) {
marker.setId(null);
return contentResolver.insert(MarkerColumns.CONTENT_URI, createContentValues(marker));
}
/**
* Delete waypoint's photo if any.
*
* @param context the context object.
* @param waypoint the waypoint object.
*/
private void deleteWaypointPhoto(Context context, Waypoint waypoint) {
if (waypoint != null && waypoint.hasPhoto()) {
Uri uri = waypoint.getPhotoURI();
File file = FileUtils.getPhotoFileIfExists(context, waypoint.getTrackId(), uri);
private void deleteMarkerPhoto(Context context, Marker marker) {
if (marker != null && marker.hasPhoto()) {
Uri uri = marker.getPhotoURI();
File file = FileUtils.getPhotoFileIfExists(context, marker.getTrackId(), uri);
if (file.exists()) {
File parent = file.getParentFile();
file.delete();
@@ -538,70 +544,67 @@ public class ContentProviderUtils {
}
/**
* Updates a waypoint.
* Returns true if successful.
*
* @param updatedWaypoint the waypoint with updated data.
* @param updateMarker the marker with updated data.
* @return true if successful.
*/
public boolean updateWaypoint(Context context, Waypoint updatedWaypoint) {
Waypoint savedWaypoint = getWaypoint(updatedWaypoint.getId());
if (!updatedWaypoint.hasPhoto()) {
deleteWaypointPhoto(context, savedWaypoint);
public boolean updateMarker(Context context, Marker updateMarker) {
Marker savedMarker = getMarker(updateMarker.getId());
if (!updateMarker.hasPhoto()) {
deleteMarkerPhoto(context, savedMarker);
}
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(updatedWaypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(updatedWaypoint.getId())});
int rows = contentResolver.update(MarkerColumns.CONTENT_URI, createContentValues(updateMarker), MarkerColumns._ID + "=?", new String[]{Long.toString(updateMarker.getId().getId())});
return rows == 1;
}
ContentValues createContentValues(Waypoint waypoint) {
ContentValues createContentValues(@NonNull Marker marker) {
ContentValues values = new ContentValues();
// Value < 0 indicates no id is available
if (waypoint.getId() >= 0) {
values.put(WaypointsColumns._ID, waypoint.getId());
if (marker.getId() != null && marker.getId().isValid()) {
values.put(MarkerColumns._ID, marker.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());
values.put(WaypointsColumns.LENGTH, waypoint.getLength());
values.put(WaypointsColumns.DURATION, waypoint.getDuration());
values.put(MarkerColumns.NAME, marker.getName());
values.put(MarkerColumns.DESCRIPTION, marker.getDescription());
values.put(MarkerColumns.CATEGORY, marker.getCategory());
values.put(MarkerColumns.ICON, marker.getIcon());
if (marker.getTrackId() != null) {
values.put(MarkerColumns.TRACKID, marker.getTrackId().getId());
}
values.put(MarkerColumns.LENGTH, marker.getLength());
values.put(MarkerColumns.DURATION, marker.getDuration());
Location location = waypoint.getLocation();
values.put(WaypointsColumns.LONGITUDE, (int) (location.getLongitude() * 1E6));
values.put(WaypointsColumns.LATITUDE, (int) (location.getLatitude() * 1E6));
values.put(WaypointsColumns.TIME, location.getTime());
Location location = marker.getLocation();
values.put(MarkerColumns.LONGITUDE, (int) (location.getLongitude() * 1E6));
values.put(MarkerColumns.LATITUDE, (int) (location.getLatitude() * 1E6));
values.put(MarkerColumns.TIME, location.getTime());
if (location.hasAltitude()) {
values.put(WaypointsColumns.ALTITUDE, location.getAltitude());
values.put(MarkerColumns.ALTITUDE, location.getAltitude());
}
if (location.hasAccuracy()) {
values.put(WaypointsColumns.ACCURACY, location.getAccuracy());
values.put(MarkerColumns.ACCURACY, location.getAccuracy());
}
if (location.hasBearing()) {
values.put(WaypointsColumns.BEARING, location.getBearing());
values.put(MarkerColumns.BEARING, location.getBearing());
}
values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl());
values.put(MarkerColumns.PHOTOURL, marker.getPhotoUrl());
return values;
}
/**
* Gets a waypoint cursor.
*
* @param projection the projection
* @param selection the selection
* @param selectionArgs the selection args
* @param sortOrder the sort order
* @param maxWaypoints the maximum number of waypoints
* @param maxCount the maximum number of markers
*/
private Cursor getWaypointCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder, int maxWaypoints) {
private Cursor getMarkerCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder, int maxCount) {
if (sortOrder == null) {
sortOrder = WaypointsColumns._ID;
sortOrder = MarkerColumns._ID;
}
if (maxWaypoints >= 0) {
sortOrder += " LIMIT " + maxWaypoints;
if (maxCount >= 0) {
sortOrder += " LIMIT " + maxCount;
}
return contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
return contentResolver.query(MarkerColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
}
/**
@@ -656,7 +659,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 +669,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 +695,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 +717,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 +748,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 +758,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 +781,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 +797,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 +850,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 +876,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 +907,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) {
@@ -33,9 +33,9 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.MarkerColumns;
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns;
/**
* A {@link ContentProvider} that handles access to track points, tracks, and waypoints tables.
@@ -61,9 +61,9 @@ public class CustomContentProvider extends ContentProvider {
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath(), UrlType.TRACKS.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath() + "/*", UrlType.TRACKS_BY_ID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI.getPath(), UrlType.WAYPOINTS.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI.getPath() + "/#", UrlType.WAYPOINTS_BY_ID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, WaypointsColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.WAYPOINTS_BY_TRACKID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.WAYPOINTS.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath() + "/#", UrlType.WAYPOINTS_BY_ID.ordinal());
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.WAYPOINTS_BY_TRACKID.ordinal());
}
@Override
@@ -101,7 +101,7 @@ public class CustomContentProvider extends ContentProvider {
shouldVacuum = true;
break;
case WAYPOINTS:
table = WaypointsColumns.TABLE_NAME;
table = MarkerColumns.TABLE_NAME;
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
@@ -139,10 +139,10 @@ public class CustomContentProvider extends ContentProvider {
case TRACKS_BY_ID:
return TracksColumns.CONTENT_ITEMTYPE;
case WAYPOINTS:
return WaypointsColumns.CONTENT_TYPE;
return MarkerColumns.CONTENT_TYPE;
case WAYPOINTS_BY_ID:
case WAYPOINTS_BY_TRACKID:
return WaypointsColumns.CONTENT_ITEMTYPE;
return MarkerColumns.CONTENT_ITEMTYPE;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
@@ -214,16 +214,16 @@ public class CustomContentProvider extends ContentProvider {
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
break;
case WAYPOINTS:
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
sortOrder = sort != null ? sort : WaypointsColumns.DEFAULT_SORT_ORDER;
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
break;
case WAYPOINTS_BY_ID:
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
queryBuilder.appendWhere(WaypointsColumns._ID + "=" + ContentUris.parseId(url));
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
queryBuilder.appendWhere(MarkerColumns._ID + "=" + ContentUris.parseId(url));
break;
case WAYPOINTS_BY_TRACKID:
queryBuilder.setTables(WaypointsColumns.TABLE_NAME);
queryBuilder.appendWhere(WaypointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
break;
default:
throw new IllegalArgumentException("Unknown url " + url);
@@ -262,12 +262,12 @@ public class CustomContentProvider extends ContentProvider {
}
break;
case WAYPOINTS:
table = WaypointsColumns.TABLE_NAME;
table = MarkerColumns.TABLE_NAME;
whereClause = where;
break;
case WAYPOINTS_BY_ID:
table = WaypointsColumns.TABLE_NAME;
whereClause = WaypointsColumns._ID + "=" + ContentUris.parseId(url);
table = MarkerColumns.TABLE_NAME;
whereClause = MarkerColumns._ID + "=" + ContentUris.parseId(url);
if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")";
}
@@ -368,9 +368,9 @@ public class CustomContentProvider extends ContentProvider {
* @param contentValues the content values
*/
private Uri insertWaypoint(Uri url, ContentValues contentValues) {
long rowId = db.insert(WaypointsColumns.TABLE_NAME, WaypointsColumns._ID, contentValues);
long rowId = db.insert(MarkerColumns.TABLE_NAME, MarkerColumns._ID, contentValues);
if (rowId >= 0) {
return ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), rowId).build();
return ContentUris.appendId(MarkerColumns.CONTENT_URI.buildUpon(), rowId).build();
}
throw new SQLException("Failed to insert a waypoint " + url);
}
@@ -11,9 +11,10 @@ import androidx.annotation.VisibleForTesting;
import java.util.UUID;
import de.dennisguse.opentracks.content.data.MarkerColumns;
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;
import de.dennisguse.opentracks.util.UUIDUtils;
/**
@@ -51,8 +52,8 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
db.execSQL(TracksColumns.CREATE_TABLE);
db.execSQL(TracksColumns.CREATE_TABLE_INDEX);
db.execSQL(WaypointsColumns.CREATE_TABLE);
db.execSQL(WaypointsColumns.CREATE_TABLE_INDEX);
db.execSQL(MarkerColumns.CREATE_TABLE);
db.execSQL(MarkerColumns.CREATE_TABLE_INDEX);
}
@Override
@@ -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(), tracks);
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext());
PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> {
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
trackExporter.writeTrack(getContext(), 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;
@@ -37,9 +37,9 @@ import de.dennisguse.opentracks.chart.ChartPoint;
import de.dennisguse.opentracks.chart.ChartView;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.data.Marker;
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.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.util.LocationUtils;
@@ -242,21 +242,21 @@ public class ChartFragment extends Fragment implements TrackDataListener {
}
@Override
public void clearWaypoints() {
public void clearMarkers() {
if (isResumed()) {
chartView.clearWaypoints();
}
}
@Override
public void onNewWaypoint(Waypoint waypoint) {
if (isResumed() && waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
chartView.addWaypoint(waypoint);
public void onNewMarker(Marker marker) {
if (isResumed() && marker != null && LocationUtils.isValidLocation(marker.getLocation())) {
chartView.addWaypoint(marker);
}
}
@Override
public void onNewWaypointsDone() {
public void onNewMarkersDone() {
if (isResumed()) {
runOnUiThread(updateChart);
}
@@ -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.Marker;
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, Marker.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,12 +64,12 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS);
final Marker.Id[] markerIds = (Marker.Id[]) getArguments().getParcelableArray(KEY_MARKER_IDS);
final FragmentActivity fragmentActivity = getActivity();
int titleId;
int messageId;
if (markerIds.length == 1 && markerIds[0] == -1L) {
if (markerIds == null) {
titleId = R.string.generic_delete_all_confirm_title;
messageId = R.string.marker_delete_all_confirm_message;
} else {
@@ -78,8 +79,8 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
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 (Marker.Id markerId : markerIds) {
contentProviderUtils.deleteMarker(getContext(), markerId);
}
caller.onDeleteMarkerDone();
}).start());
@@ -206,10 +206,10 @@ public class ExportProgressDialogFragment extends DialogFragment {
}
private Boolean exportTrack(Context context, Track track) {
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track});
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);
@@ -218,7 +218,7 @@ public class ExportProgressDialogFragment extends DialogFragment {
}
try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) {
if (trackExporter.writeTrack(context, outputStream)) {
if (trackExporter.writeTrack(track, outputStream)) {
return true;
} else {
if (!file.delete()) {
@@ -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,105 @@
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());
}
});
}
@Override
public void unitChanged() {
if (viewModel != null) {
LiveData<IntervalStatistics> liveData = viewModel.getIntervalStats(trackId, null);
liveData.observe(getActivity(), intervalStatistics -> {
if (intervalStatistics != null) {
intervalListView.display(intervalStatistics.getIntervalList());
}
});
}
}
}
@@ -0,0 +1,147 @@
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.TrackRecordingActivity;
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, TrackRecordingActivity.OnTrackIdListener {
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 IntervalStatisticsModel.IntervalOption selectedInterval;
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);
((TrackRecordingActivity) getActivity()).setTrackIdListener(this);
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;
}
selectedInterval = interval;
LiveData<IntervalStatistics> liveData = viewModel.getIntervalStats(trackId, interval);
liveData.observe(getActivity(), intervalStatistics -> {
if (intervalStatistics != null) {
intervalListView.display(intervalStatistics.getIntervalList());
}
});
}
private void updateIntervals() {
intervalChanged(selectedInterval);
}
@Override
public void unitChanged() {
if (viewModel != null) {
LiveData<IntervalStatistics> liveData = viewModel.getIntervalStats(trackId, selectedInterval);
liveData.observe(getActivity(), intervalStatistics -> {
if (intervalStatistics != null) {
intervalListView.display(intervalStatistics.getIntervalList());
}
});
}
}
@Override
public void onTrackId(Track.Id trackId) {
this.trackId = trackId;
}
}
@@ -40,7 +40,7 @@ import androidx.fragment.app.FragmentActivity;
import de.dennisguse.opentracks.MarkerEditActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils;
@@ -67,7 +67,7 @@ public class MarkerDetailFragment extends Fragment {
private ImageView photoView;
private ImageView textGradient;
private LinearLayout waypointInfo;
private Waypoint waypoint;
private Marker waypoint;
private final Runnable hideText = new Runnable() {
@Override
public void run() {
@@ -93,9 +93,9 @@ public class MarkerDetailFragment extends Fragment {
}
};
public static MarkerDetailFragment newInstance(long markerId) {
public static MarkerDetailFragment newInstance(Marker.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) {
Marker.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);
Marker.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.getMarker(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);
@@ -22,9 +22,9 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordingActivity;
import de.dennisguse.opentracks.content.TrackDataHub;
import de.dennisguse.opentracks.content.TrackDataListener;
import de.dennisguse.opentracks.content.data.Marker;
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.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
@@ -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());
@@ -346,17 +350,17 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
}
@Override
public void clearWaypoints() {
public void clearMarkers() {
// We don't care.
}
@Override
public void onNewWaypoint(Waypoint wpt) {
public void onNewMarker(Marker marker) {
// We don't care.
}
@Override
public void onNewWaypointsDone() {
public void onNewMarkersDone() {
// We don't care.
}
@@ -5,7 +5,6 @@ import android.content.Context;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.FileTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter;
@@ -21,8 +20,8 @@ public enum TrackFileFormat {
KML_ONLY_TRACK {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, false, false, false);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, false, false, false);
}
@Override
@@ -36,8 +35,8 @@ public enum TrackFileFormat {
},
KML_WITH_TRACKDETAIL {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, false, false);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, false, false);
}
@Override
@@ -51,8 +50,8 @@ public enum TrackFileFormat {
},
KML_WITH_TRACKDETAIL_AND_SENSORDATA {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, true, false);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, true, false);
}
@Override
@@ -69,12 +68,12 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false;
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, false, false, exportPhotos);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, false, false, exportPhotos);
}
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, this.newTrackWriter(context), exportPhotos);
}
@Override
@@ -96,12 +95,12 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false;
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, false, exportPhotos);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, false, exportPhotos);
}
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, this.newTrackWriter(context), exportPhotos);
}
@Override
@@ -124,8 +123,8 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false;
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, true, exportPhotos);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, true, exportPhotos);
}
@Override
@@ -133,8 +132,8 @@ public enum TrackFileFormat {
return MIME_KMZ;
}
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, this.newTrackWriter(context), exportPhotos);
}
public String getExtension() {
@@ -153,8 +152,8 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = true;
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
return new KmlTrackWriter(context, multiple, true, true, exportPhotos);
public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, true, true, exportPhotos);
}
@Override
@@ -162,8 +161,8 @@ public enum TrackFileFormat {
return MIME_KMZ;
}
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, exportPhotos);
public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, newTrackWriter(context), exportPhotos);
}
public String getExtension() {
@@ -178,7 +177,7 @@ public enum TrackFileFormat {
},
GPX {
@Override
public TrackWriter newTrackWriter(Context context, boolean multiple) {
public TrackWriter newTrackWriter(Context context) {
return new GpxTrackWriter(context.getString(R.string.app_name));
}
@@ -196,12 +195,12 @@ public enum TrackFileFormat {
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, boolean exportPhotos) {
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, boolean exportPhotos) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks);
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter);
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos);
return new KmzTrackExporter(context, contentProviderUtils, fileTrackExporter, exportPhotos);
}
/**
@@ -209,19 +208,18 @@ public enum TrackFileFormat {
*/
public abstract String getMimeType();
public TrackExporter newTrackExporter(Context context, Track[] tracks) {
public TrackExporter newTrackExporter(Context context) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
TrackWriter trackWriter = newTrackWriter(context, tracks.length > 1);
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks);
TrackWriter trackWriter = newTrackWriter(context);
return new FileTrackExporter(contentProviderUtils, trackWriter);
}
/**
* Creates a new track writer for the format.
*
* @param context the context
* @param multiple true for writing multiple tracks
* @param context the context
*/
public abstract TrackWriter newTrackWriter(Context context, boolean multiple);
public abstract TrackWriter newTrackWriter(Context context);
/**
* Returns the file extension for each format.
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
@@ -24,9 +23,9 @@ import androidx.annotation.NonNull;
import java.io.OutputStream;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.util.LocationUtils;
@@ -42,7 +41,6 @@ public class FileTrackExporter implements TrackExporter {
private static final String TAG = FileTrackExporter.class.getSimpleName();
private final ContentProviderUtils contentProviderUtils;
private final Track[] tracks;
private final TrackWriter trackWriter;
/**
@@ -50,29 +48,37 @@ public class FileTrackExporter implements TrackExporter {
*
* @param contentProviderUtils the content provider utils
* @param trackWriter the track writer
* @param tracks the tracks
*/
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter, Track[] tracks) {
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter) {
this.contentProviderUtils = contentProviderUtils;
this.tracks = tracks;
this.trackWriter = trackWriter;
}
@Override
public boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream) {
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
return writeTrack(new Track[]{track}, outputStream);
}
@Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
try {
trackWriter.prepare(outputStream);
trackWriter.writeHeader(tracks);
for (Track track1 : tracks) {
writeWaypoints(track1);
}
trackWriter.writeBeginTracks();
boolean hasMultipleTracks = tracks.length > 1;
if (hasMultipleTracks) {
trackWriter.writeMultiTrackBegin();
}
long startTime = tracks[0].getTrackStatistics().getStartTime_ms();
for (Track track : tracks) {
long offset = track.getTrackStatistics().getStartTime_ms() - startTime;
writeLocations(track, offset);
}
trackWriter.writeEndTracks();
if (hasMultipleTracks) {
trackWriter.writeMultiTrackEnd();
}
trackWriter.writeFooter();
trackWriter.close();
@@ -92,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.getMarkerCursor(track.getId(), null, ContentProviderUtils.MAX_LOADED_MARKERS)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) {
@@ -102,7 +108,7 @@ public class FileTrackExporter implements TrackExporter {
trackWriter.writeBeginWaypoints(track);
hasWaypoints = true;
}
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
Marker waypoint = contentProviderUtils.createMarker(cursor);
trackWriter.writeWaypoint(waypoint);
cursor.moveToNext();
@@ -121,7 +127,6 @@ public class FileTrackExporter implements TrackExporter {
boolean wroteTrack = false;
boolean wroteSegment = false;
boolean isLastLocationValid = false;
int locationNumber = 0;
TrackPoint lastTrackPoint = null;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
@@ -133,7 +138,6 @@ public class FileTrackExporter implements TrackExporter {
TrackPoint trackPoint = trackPointIterator.next();
setLocationTime(trackPoint, offset);
locationNumber++;
boolean isLocationValid = LocationUtils.isValidLocation(trackPoint.getLocation());
boolean isSegmentValid = isLocationValid && isLastLocationValid;
@@ -23,9 +23,9 @@ import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.Locale;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.util.StringUtils;
/**
@@ -130,7 +130,7 @@ public class GpxTrackWriter implements TrackWriter {
}
@Override
public void writeWaypoint(Waypoint waypoint) {
public void writeWaypoint(Marker waypoint) {
if (printWriter != null) {
Location location = waypoint.getLocation();
printWriter.println("<wpt " + formatLocation(location) + ">");
@@ -146,12 +146,12 @@ public class GpxTrackWriter implements TrackWriter {
}
@Override
public void writeBeginTracks() {
public void writeMultiTrackBegin() {
// Do nothing
}
@Override
public void writeEndTracks() {
public void writeMultiTrackEnd() {
// Do nothing
}
@@ -26,9 +26,9 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.DescriptionGenerator;
import de.dennisguse.opentracks.content.data.Marker;
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.util.FileUtils;
import de.dennisguse.opentracks.util.StringUtils;
@@ -57,7 +57,6 @@ public class KmlTrackWriter implements TrackWriter {
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private final Context context;
private final boolean hasMultipleTracks;
private final boolean exportPhotos;
private final boolean exportTrackDetail;
private final boolean exportSensorData;
@@ -74,14 +73,12 @@ public class KmlTrackWriter implements TrackWriter {
/**
* @param context the context
* @param hasMultipleTracks should encode multiple tracks into one file?
* @param exportTrackDetail should detailed information about the track be exported (e.g., title, description, waypoints, timing)?
* @param exportSensorData should {@link TrackPoint}'s sensor data be exported?
* @param exportPhotos should pictures be exported (if true: exports to KMZ)?
*/
public KmlTrackWriter(Context context, boolean hasMultipleTracks, boolean exportTrackDetail, boolean exportSensorData, boolean exportPhotos) {
public KmlTrackWriter(Context context, boolean exportTrackDetail, boolean exportSensorData, boolean exportPhotos) {
this.context = context;
this.hasMultipleTracks = hasMultipleTracks;
this.exportTrackDetail = exportTrackDetail;
this.exportSensorData = exportSensorData;
this.exportPhotos = exportPhotos;
@@ -166,7 +163,7 @@ public class KmlTrackWriter implements TrackWriter {
}
@Override
public void writeWaypoint(Waypoint waypoint) {
public void writeWaypoint(Marker waypoint) {
if (printWriter != null && exportTrackDetail) {
boolean existsPhoto = FileUtils.getPhotoFileIfExists(context, waypoint.getTrackId(), waypoint.getPhotoURI()) != null;
if (waypoint.hasPhoto() && exportPhotos && existsPhoto) {
@@ -178,18 +175,16 @@ public class KmlTrackWriter implements TrackWriter {
}
}
@Override
public void writeBeginTracks() {
if (printWriter != null && hasMultipleTracks) {
public void writeMultiTrackBegin() {
if (printWriter != null) {
printWriter.println("<Folder id=tour>");
printWriter.println("<name>" + context.getString(R.string.generic_tracks) + "</name>");
printWriter.println("<open>1</open>");
}
}
@Override
public void writeEndTracks() {
if (printWriter != null && hasMultipleTracks) {
public void writeMultiTrackEnd() {
if (printWriter != null) {
printWriter.println("</Folder>");
}
}
@@ -340,7 +335,7 @@ public class KmlTrackWriter implements TrackWriter {
* @param waypoint Waypoint object.
* @param heading the heading.
*/
private void writePhotoOverlay(Waypoint waypoint, float heading) {
private void writePhotoOverlay(Marker waypoint, float heading) {
if (exportTrackDetail) {
printWriter.println("<PhotoOverlay>");
printWriter.println("<name>" + StringUtils.formatCData(waypoint.getName()) + "</name>");
@@ -393,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();
@@ -31,8 +31,8 @@ import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import de.dennisguse.opentracks.content.data.Marker;
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.util.FileUtils;
@@ -51,35 +51,37 @@ public class KmzTrackExporter implements TrackExporter {
private final ContentProviderUtils contentProviderUtils;
private final FileTrackExporter fileTrackExporter;
private final Track[] tracks;
private final boolean exportPhotos;
private Context context;
/**
* Constructor.
*
* @param context the context
* @param contentProviderUtils the content provider utils
* @param trackExporter the file track exporter
* @param tracks the tracks to export
*/
public KmzTrackExporter(ContentProviderUtils contentProviderUtils, FileTrackExporter trackExporter, Track[] tracks, boolean exportPhotos) {
public KmzTrackExporter(Context context, ContentProviderUtils contentProviderUtils, FileTrackExporter trackExporter, boolean exportPhotos) {
this.context = context;
this.contentProviderUtils = contentProviderUtils;
this.fileTrackExporter = trackExporter;
this.tracks = tracks;
this.exportPhotos = exportPhotos;
}
@Override
public boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream) {
ZipOutputStream zipOutputStream = null;
try {
zipOutputStream = new ZipOutputStream(outputStream);
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
return writeTrack(new Track[]{track}, outputStream);
}
@Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
// Add kml file
ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE);
zipOutputStream.putNextEntry(zipEntry);
boolean success = fileTrackExporter.writeTrack(context, zipOutputStream);
boolean success = fileTrackExporter.writeTrack(tracks, zipOutputStream);
zipOutputStream.closeEntry();
if (!success) {
Log.e(TAG, "Unable to write kml in kmz");
@@ -87,31 +89,23 @@ public class KmzTrackExporter implements TrackExporter {
}
// Add photos
if (exportPhotos) addImages(context, zipOutputStream);
if (exportPhotos) addImages(context, tracks ,zipOutputStream);
return true;
} 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, ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
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.getMarkerCursor(track.getId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
Marker waypoint = contentProviderUtils.createMarker(cursor);
if (waypoint.hasPhoto()) {
Uri uriPhoto = waypoint.getPhotoURI();
boolean existsPhoto = FileUtils.getPhotoFileIfExists(context, track.getId(), uriPhoto) != null;
@@ -127,7 +121,7 @@ public class KmzTrackExporter implements TrackExporter {
}
}
private void addImage(Context context, ZipOutputStream zipOutputStream, Uri uri, Waypoint waypoint) throws IOException {
private void addImage(Context context, ZipOutputStream zipOutputStream, Uri uri, Marker waypoint) throws IOException {
try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
ZipEntry zipEntry = new ZipEntry(buildKmzImageFilePath(waypoint));
zipOutputStream.putNextEntry(zipEntry);
@@ -156,7 +150,7 @@ public class KmzTrackExporter implements TrackExporter {
*
* @param waypoint Waypoint object.
*/
public static String buildKmzImageFilePath(Waypoint waypoint) {
public static String buildKmzImageFilePath(Marker waypoint) {
String ext = FileUtils.getExtension(waypoint.getPhotoUrl());
ext = ext == null ? "" : "." + ext;
return KMZ_IMAGES_DIR + File.separatorChar + FileUtils.sanitizeFileName(waypoint.getId() + ext);
@@ -16,12 +16,12 @@
package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import androidx.annotation.NonNull;
import java.io.OutputStream;
import de.dennisguse.opentracks.content.data.Track;
/**
* Track exporting for exporting track to an {@link OutputStream}.
*
@@ -29,12 +29,7 @@ import java.io.OutputStream;
*/
public interface TrackExporter {
/**
* Write track to an output stream.
* Depending on the implementation a context might be required.
*
* @param context the context
* @param outputStream the output stream
*/
boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream);
boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream);
boolean writeTrack(Track track, @NonNull OutputStream outputStream);
}
@@ -17,9 +17,9 @@ package de.dennisguse.opentracks.io.file.exporter;
import java.io.OutputStream;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
/**
* Interface for writing tracks to a file. The expected sequence of calls is:
@@ -30,9 +30,9 @@ import de.dennisguse.opentracks.content.data.Waypoint;
* For each track:
* {@link #writeBeginWaypoints(Track)}
* For each waypoint:
* {@link #writeWaypoint(Waypoint)}
* {@link #writeWaypoint(Marker)}
* {@link #writeEndWaypoints()}
* {@link #writeBeginTracks()}
* {@link #writeMultiTrackBegin()}
* For each track:
* {@link #writeBeginTrack(Track, TrackPoint)}
* For each segment:
@@ -41,7 +41,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
* {@link #writeTrackPoint(TrackPoint)}
* {@link #writeCloseSegment()}
* {@link #writeEndTrack(Track, TrackPoint)}
* {@link #writeEndTracks()}
* {@link #writeMultiTrackEnd()}
* {@link #writeFooter()}
* {@link #close()}
* </pre>
@@ -91,17 +91,17 @@ public interface TrackWriter {
*
* @param waypoint the waypoint
*/
void writeWaypoint(Waypoint waypoint);
void writeWaypoint(Marker waypoint);
/**
* Writes the beginning of the tracks.
*/
void writeBeginTracks();
void writeMultiTrackBegin();
/**
* Writes the end of the tracks,
*/
void writeEndTracks();
void writeMultiTrackEnd();
/**
* Writes the beginning of a track.
@@ -39,9 +39,9 @@ import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
@@ -68,9 +68,9 @@ 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 final List<Waypoint> waypoints = new ArrayList<>();
private Track.Id importTrackId;
private final List<Track.Id> trackIds = new ArrayList<>();
private final List<Marker> waypoints = new ArrayList<>();
// The current element content
//TODO Should be made private and getter be used by child classes.
@@ -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,14 +153,14 @@ 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;
}
int waypointPosition = -1;
Waypoint waypoint = null;
Marker waypoint = null;
TrackPoint trackPoint = null;
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics().getStartTime_ms());
// TODO Should not be necessary anymore?
@@ -209,8 +209,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
long duration = trackStatisticsUpdater.getTrackStatistics().getTotalTime();
// Insert waypoint
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription, waypoint.getCategory(), icon, track.getId(), length, duration, trackPoint.getLocation(), waypoint.getPhotoUrl());
contentProviderUtils.insertWaypoint(newWaypoint);
Marker newWaypoint = new Marker(waypoint.getName(), waypointDescription, waypoint.getCategory(), icon, track.getId(), length, duration, trackPoint.getLocation(), waypoint.getPhotoUrl());
contentProviderUtils.insertMarker(newWaypoint);
}
// Load the next waypoint
@@ -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));
@@ -314,7 +314,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) {
throw new SAXException(createErrorMessage("Invalid location detected: " + trackPoint));
}
Waypoint waypoint = new Waypoint(trackPoint.getLocation());
Marker waypoint = new Marker(trackPoint.getLocation());
if (name != null) {
waypoint.setName(name);
@@ -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,7 +32,8 @@ import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
import de.dennisguse.opentracks.util.FileUtils;
@@ -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,13 +196,13 @@ 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);
List<Marker> waypoints = contentProviderUtils.getMarkers(trackId);
List<String> photosName = new ArrayList<>();
for (Waypoint w : waypoints) {
for (Marker w : waypoints) {
if (w.hasPhoto()) {
String photoUrl = Uri.decode(w.getPhotoUrl());
photosName.add(photoUrl.substring(photoUrl.lastIndexOf(File.separatorChar) + 1));
@@ -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);
}
@@ -39,9 +39,9 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.TrackRecordingActivity;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
@@ -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;
}
@@ -215,6 +215,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return trackStatisticsUpdater.getTrackStatistics();
}
//TODO Throw exception, when not recording.
public long getTotalTime() {
if (trackStatisticsUpdater == null) {
return 0;
@@ -225,28 +226,23 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return trackStatisticsUpdater.getTrackStatistics().getTotalTime();
}
/**
* Inserts a waypoint.
*
* @return the waypoint id
*/
public long insertWaypoint(String name, String category, String description, String photoUrl) {
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
if (!isRecording() || isPaused()) {
return -1L;
return null;
}
if (name == null) {
int nextWaypointNumber = contentProviderUtils.getNextWaypointNumber(recordingTrackId);
if (nextWaypointNumber == -1) {
nextWaypointNumber = 1;
int nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(recordingTrackId);
if (nextMarkerNumber == -1) {
nextMarkerNumber = 1;
}
name = getString(R.string.marker_name_format, nextWaypointNumber + 1);
name = getString(R.string.marker_name_format, nextMarkerNumber + 1);
}
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId);
if (trackPoint == null) {
Log.i(TAG, "Could not create a waypoint as trackPoint is unknown.");
return -1L;
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
return null;
}
category = category != null ? category : "";
@@ -258,10 +254,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
double length = stats.getTotalDistance();
long duration = stats.getTotalTime();
// 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);
// Insert marker
Marker marker = new Marker(name, description, category, icon, recordingTrackId, length, duration, trackPoint.getLocation(), photoUrl);
Uri uri = contentProviderUtils.insertMarker(marker);
return new Marker.Id(ContentUris.parseId(uri));
}
/**
@@ -269,10 +265,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 +276,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 +301,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 +329,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 +399,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 +485,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 +503,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,6 +2,8 @@ package de.dennisguse.opentracks.services;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
@@ -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,8 +90,8 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
}
@Override
public long insertWaypoint(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertWaypoint(name, category, description, photoUrl);
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertMarker(name, category, description, photoUrl);
}
@Override
@@ -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.Marker;
import de.dennisguse.opentracks.content.data.Track;
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 Marker.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) {
Marker.Id marker = trackRecordingService.insertMarker(name, category, description, photoUrl);
if (marker != null) {
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
return markerId;
return marker;
}
} catch (IllegalStateException e) {
Log.e(TAG, "Unable to add marker.", e);
@@ -196,15 +198,9 @@ public class TrackRecordingServiceConnection implements ServiceConnection, Death
}
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
return -1L;
return null;
}
/**
* Stops the recording.
*
* @param context the context
* @param showEditor true to show the editor
*/
public void stopRecording(@NonNull Context context, boolean showEditor) {
TrackRecordingServiceInterface trackRecordingService = getServiceIfBound();
if (trackRecordingService == null) {
@@ -213,7 +209,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,6 +17,8 @@ package de.dennisguse.opentracks.services;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
@@ -28,46 +30,20 @@ import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
*/
public interface TrackRecordingServiceInterface {
/**
* Starts gps.
*/
void startGps();
/**
* Stops gps.
*/
void stopGps();
/**
* Starts recording a new track.
*
* @return the track ID of the new track.
*/
long startNewTrack();
Track.Id startNewTrack();
/**
* Resumes the track identified by trackId.
*/
void resumeTrack(long trackId);
/**
* Pauses the current recording track.
*/
void pauseCurrentTrack();
/**
* Resumes the current recording track.
*/
void resumeCurrentTrack();
/**
* Ends the current recording track.
*/
void resumeTrack(Track.Id trackId);
void endCurrentTrack();
/**
* Returns true if currently recording a track.
*/
boolean isRecording();
/**
@@ -76,21 +52,17 @@ 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();
/**
* Inserts a waypoint in the current recording track.
*
* @return the ID of the inserted waypoint
*/
long insertWaypoint(String name, String category, String description, String photoUrl);
Marker.Id insertMarker(String name, String category, String description, String photoUrl);
/**
* Gets the current sensor data. Returns null if there is no data.
@@ -32,6 +32,9 @@ class GpsStatus {
// The last valid (not null) location. Null value means that there have not been any location yet.
private Location lastValidLocation = null;
// Flag to prevent GpsStatus checks two or more locations at the same time.
private boolean checking = false;
private class GpsStatusRunner implements Runnable {
private boolean stopped = false;
@@ -99,16 +102,24 @@ class GpsStatus {
* 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) {
if (checking == true) {
return;
}
checking = true;
if (lastLocation != null) {
checkStatusFromLastLocation();
} else if (lastValidLocation != null) {
checkStatusFromLastValidLocation();
}
lastLocation = location;
if (location != null) {
// Update location's time to the current time millis when location has been received.
location.setTime(System.currentTimeMillis());
lastValidLocation = location;
}
lastLocation = location;
checking = false;
}
/**
@@ -119,7 +130,7 @@ class GpsStatus {
*/
private void checkStatusFromLastLocation() {
if (System.currentTimeMillis() - lastLocation.getTime() > signalLostThreshold && gpsStatus != GpsStatusValue.GPS_SIGNAL_LOST) {
// So much time without receiving signal -> signal lost.
// Too much time without receiving signal -> signal lost.
GpsStatusValue oldStatus = gpsStatus;
gpsStatus = GpsStatusValue.GPS_SIGNAL_LOST;
sendStatus(oldStatus, gpsStatus);
@@ -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,10 +8,13 @@ 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.MarkerColumns;
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;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
/**
@@ -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;
}
@@ -52,7 +55,7 @@ public class IntentDashboardUtils {
ArrayList<Uri> uris = new ArrayList<>();
uris.add(TRACK_URI_INDEX, Uri.withAppendedPath(TracksColumns.CONTENT_URI, trackIdList));
uris.add(TRACKPOINTS_URI_INDEX, Uri.withAppendedPath(TrackPointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
uris.add(WAYPOINTS_URI_INDEX, Uri.withAppendedPath(WaypointsColumns.CONTENT_URI_BY_TRACKID, trackIdList));
uris.add(WAYPOINTS_URI_INDEX, Uri.withAppendedPath(MarkerColumns.CONTENT_URI_BY_TRACKID, trackIdList));
Intent intent = new Intent(ACTION_DASHBOARD);
intent.putParcelableArrayListExtra(ACTION_DASHBOARD_PAYLOAD, uris);
@@ -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;
@@ -32,8 +32,8 @@ import java.util.Date;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.DescriptionGenerator;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.ShareContentProvider;
@@ -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;
}
@@ -128,7 +128,7 @@ public class IntentUtils {
.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_image_body));
}
public static void showCoordinateOnMap(Context context, Waypoint waypoint) {
public static void showCoordinateOnMap(Context context, Marker waypoint) {
showCoordinateOnMap(context, waypoint.getLocation().getLatitude(), waypoint.getLocation().getLongitude(), waypoint.getName());
}
@@ -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());

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