Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b9711d82e | |||
| 114fc8255f | |||
| 26e655d58f | |||
| 815b6a3cf1 | |||
| 3dc8a724d8 | |||
| 70f571cd09 | |||
| 3f5c3a5c08 | |||
| a13465391f | |||
| 97454b5369 | |||
| f6a47c4594 | |||
| fce76230e1 | |||
| fdf4336a9b | |||
| 844c6e9609 | |||
| 784942ffd2 | |||
| edf4ccc941 | |||
| d90258294f | |||
| 01f4c13307 | |||
| 873956426d | |||
| 4128c54960 | |||
| 9978bddf21 | |||
| a9726b9a94 | |||
| 05ba52bab5 | |||
| ca95f40e77 | |||
| 5cb85c4383 | |||
| d0a477b112 | |||
| ec365a18ca | |||
| 0ff735e559 | |||
| 8a35835791 | |||
| e3569dab56 | |||
| 4040e3f45b | |||
| eac7efba64 | |||
| 21a3971a56 | |||
| 4e4a9abf6f | |||
| a9624f72e7 | |||
| bb38d274fb | |||
| 418f092db9 | |||
| fa12086951 | |||
| 4a340380ac | |||
| cd4e0abd6f | |||
| 854c8727e5 | |||
| 2f3d9a29f3 | |||
| 18cdbe94a9 | |||
| 18e179cfe3 | |||
| 63e3c7a4e7 | |||
| c6deca9bbd | |||
| 9ef6ae5e25 | |||
| 29b1ded68c | |||
| 6239e2fcad | |||
| bdfcd9c89f | |||
| f8f78d28dd | |||
| cbe469d5e4 | |||
| e0553f812f | |||
| 5aa016b5b9 | |||
| c3741611dc | |||
| d157e94220 | |||
| 022d8146fe | |||
| 912fd1aa2f | |||
| 1d46981717 | |||
| e990b413cd | |||
| 3bbcbfe202 | |||
| 9d8f49f1dc | |||
| 9f6d6711e5 | |||
| f635d56ceb | |||
| ab47320216 | |||
| 47b2d0748a | |||
| bc66cd33a9 | |||
| fcc17413e3 | |||
| 163af2dab2 | |||
| 393e2022a1 | |||
| 793928a5a6 | |||
| 50bb0b14b4 | |||
| 8a08e7b2b5 | |||
| e9e9fdcc3f | |||
| 83c471ccb6 | |||
| 17479c438b | |||
| 943c80f9ec | |||
| 23751306a4 | |||
| 78b712a291 | |||
| f8432adaa0 | |||
| 80b26f2a04 | |||
| 41e6e80cfc | |||
| 7e72b29c3f | |||
| 860df0197e | |||
| f1d791a62c | |||
| e9551bea36 | |||
| 577b978a5c | |||
| a689b6eb2c | |||
| ccd93dd17d | |||
| f4fe1d8d97 | |||
| d1290c8f22 | |||
| a340cb57de | |||
| 75b8f3b9cf | |||
| 9462221a28 | |||
| af82eae8a4 | |||
| 1c4946e85b | |||
| fd1b7b8af9 | |||
| eb02c4c469 | |||
| 4714171e21 | |||
| 4bff376442 | |||
| c14413cbe7 | |||
| 778fb4b346 | |||
| 130552f0eb | |||
| 41eca663e8 | |||
| 6c79702075 | |||
| 6d109a1c5a | |||
| b71904a3bd | |||
| ef28784682 | |||
| 05b7242191 | |||
| 94c69f92a6 | |||
| 8f12ba3d48 | |||
| 190a572951 | |||
| 7ae8809101 | |||
| 19a7ef3217 | |||
| f261e8ab9d | |||
| a41160229d | |||
| 2fa8b998f9 | |||
| 99847dbed9 | |||
| 483736637e | |||
| e133743547 | |||
| eb2c2b4d78 | |||
| 1409537e56 | |||
| 76c3699518 | |||
| 5675dc800d | |||
| e1e7c24d9f | |||
| 2733071a4d | |||
| 2255624235 | |||
| 55fa6d2d79 | |||
| 96371336f4 | |||
| 1862e38a20 |
@@ -19,9 +19,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
|
||||
@@ -18,4 +18,6 @@ jobs:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
target: ${{ matrix.target }}
|
||||
arch: x86_64
|
||||
profile: Nexus 6
|
||||
script: ./gradlew connectedCheck
|
||||
|
||||
|
||||
@@ -84,18 +84,16 @@ However, this is rather slow and does not provide updates while recording.
|
||||
|
||||
## Screenshots
|
||||
<div>
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_1.png">
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_2.png">
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_3.png">
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_4.png">
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_5.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot1.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot2.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot3.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot4.png">
|
||||
</div>
|
||||
<div>
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_6.png">
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_7.png">
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_8.png">
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_9.png">
|
||||
<img width="18%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot_10.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot5.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot6.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot7.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot8.png">
|
||||
</div>
|
||||
|
||||
## Project history
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Folder where svg screenshots are expected.
|
||||
SRC_DIR="screenshots-svg/"
|
||||
# Folder where png files will be saved.
|
||||
DST_DIR="fastlane/metadata/android/en-US/phoneScreenshots/"
|
||||
|
||||
echo "Exporting svg files to png..."
|
||||
|
||||
# Source and destination folder must exist.
|
||||
if [ ! -d "$SRC_DIR" ]
|
||||
then
|
||||
echo "\nERROR: ${SRC_DIR} doesn't exist."
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ ! -d "$DST_DIR" ]
|
||||
then
|
||||
echo "\nERROR: ${DST_DIR} doesn't exist."
|
||||
exit
|
||||
fi
|
||||
|
||||
# It needs Inkscape that is the tool used to convert svg to png.
|
||||
if ! command -v inkscape > /dev/null
|
||||
then
|
||||
echo "\nERROR: inkscape couldn't be found and it's needed to convert screenshots from svg to png."
|
||||
exit
|
||||
fi
|
||||
|
||||
# Convert all svg files from source directory.
|
||||
for path in ${SRC_DIR}/*.svg
|
||||
do
|
||||
filename="${path##*/}"
|
||||
basename="${filename%.svg}"
|
||||
`inkscape ${path} --export-filename ${DST_DIR}/${basename}.png`
|
||||
done
|
||||
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||
classpath 'com.android.tools.build:gradle:4.1.2'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 3761
|
||||
versionName "v3.14.0"
|
||||
versionCode 3890
|
||||
versionName "v3.15.3"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -86,17 +86,18 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha04'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
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.1'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
|
||||
|
||||
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.6.28'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.8.0'
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ class Track {
|
||||
}
|
||||
Track o-- TrackPoint
|
||||
Track o-- Marker
|
||||
Track "1" - "1" TripStatistics
|
||||
Track "1" - "1" TrackStatistics
|
||||
|
||||
class TripStatistics {
|
||||
class TrackStatistics {
|
||||
- startTime
|
||||
- stopTime
|
||||
- totalDistance
|
||||
@@ -21,13 +21,15 @@ class TripStatistics {
|
||||
- totalElevationGain
|
||||
}
|
||||
|
||||
note left of TripStatistics
|
||||
The time is based upon device time.
|
||||
end note
|
||||
class TrackStatisticsUpdater {
|
||||
}
|
||||
|
||||
TrackStatisticsUpdater ..> TrackStatistics : creates/updates
|
||||
TrackStatisticsUpdater ..> TrackPoint : uses
|
||||
|
||||
class TrackPoint {
|
||||
- id
|
||||
- id (database id, order)
|
||||
- type
|
||||
- trackId
|
||||
- longitude
|
||||
- latitude
|
||||
@@ -41,30 +43,6 @@ class TrackPoint {
|
||||
- sensor_power
|
||||
}
|
||||
|
||||
note right of TrackPoint
|
||||
TODO
|
||||
TrackPoints are mixing two concepts:
|
||||
1. a GPS measurement provided by the GPS hardware
|
||||
2. and also segment markers.
|
||||
These are marked by PAUSE / RESUME TrackPoints.
|
||||
|
||||
While exporting PAUSE/RESUME are not used to create these segments and these are partly recreated when imported.
|
||||
However, the timestamp of PAUSE/RESUME is not exported and can therefore not be restored.
|
||||
This prevents us from restoring the exact TripStatistics.
|
||||
It is also noteworthy that neither PAUSE/RESUME TrackPoints are used for computing the TrackStatistics.
|
||||
Only the timestamp to _create_ those is used.
|
||||
end note
|
||||
|
||||
note left of TrackPoint
|
||||
TODO
|
||||
The time stored in TrackPoints is either:
|
||||
1. created via GPS: provided by the GPS hardware
|
||||
2. created by the user (pause/resume): device time.
|
||||
|
||||
We know that the GPS time and device time are often not in sync.
|
||||
end note
|
||||
|
||||
TrackPoint "1" - "1" android.location.Location
|
||||
|
||||
class Marker {
|
||||
- id
|
||||
@@ -75,8 +53,28 @@ class Marker {
|
||||
- icon
|
||||
- length
|
||||
- duration
|
||||
- location
|
||||
- tripStatistics
|
||||
- longitude
|
||||
- latitude
|
||||
- photoUrl
|
||||
}
|
||||
|
||||
note left of TrackPoint
|
||||
As of OpenTracks version 3.15.0, all times are using device time.
|
||||
Before that TrackPoint.time contained GPS time (determined by GPS hardware).
|
||||
However, start/pause/stop events (also stored as TrackPoints) used device time.
|
||||
end note
|
||||
|
||||
note right of Track
|
||||
A track is an ordered collection of one or more segments (i.e., continuous parts of distance covered).
|
||||
Segments may be started by a user (i.e., start a track recording, continue a paused track, or resume a track) as well as stopped by the user (i.e., pausing or stopping a recording).
|
||||
Also segments may started automatically while recording (i.e., distance to previous location was to large).
|
||||
Note that this finishes the previous segment.
|
||||
|
||||
Segment data is stored as TrackPoints (Type.SEGMENT_START_MANUAL, Type.SEGMENT_START_AUTOMATIC, or Type.SEGMENT_END_MANUAL).
|
||||
Trackpoints with Type.SEGMENT_START_AUTOMATIC also mark the end of the previous segment.
|
||||
All TrackPoints of Type.TRACKPOINT belong to the segment started by the prior TrackPoint with Type.SEGMENT_START_(MANUAL|AUTOMATIC).
|
||||
Trackpoints of Type.SEGMENT_START_MANUAL or Type.SEGMENT_END_MANUAL do not contain location data or sensor data.
|
||||
|
||||
Tracks recorded prior to OpenTracks version 3.15.0, do neither begin with a Type.SEGMENT_START_MANUAL nor end with a Type.SEGMENT_END_MANUAL.
|
||||
end note
|
||||
@enduml
|
||||
@@ -0,0 +1,14 @@
|
||||
v3.14.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Added elevation loss (via barometer)
|
||||
- Updated translations (cz, gl, it, nb, pl, pt, sp)
|
||||
- Markers can be shared from list
|
||||
- Actionbar icon (top left) to go back
|
||||
-
|
||||
Bugfix:
|
||||
- Potential crash on import
|
||||
|
||||
Developer:
|
||||
- Implemented basic EspressoUI test
|
||||
- Reference resourceIds via if rather than switch/case
|
||||
@@ -0,0 +1,10 @@
|
||||
v3.14.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Downgrade database from upcoming release v3.15.0
|
||||
- Import/Export UI improved
|
||||
- Import offers to show imported track
|
||||
- Marker list performance improved
|
||||
|
||||
Developer:
|
||||
- Track.Id/Marker.Id: use null
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.14.3: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Downgrading database from upcoming release v3.15.0
|
||||
@@ -0,0 +1,22 @@
|
||||
v3.15.0: OpenTracks
|
||||
|
||||
WARNING:
|
||||
This release applies database changes that CANNOT BE REVERTED.
|
||||
As KMZ/KML export/import behaviour changes as well, it is recommended to (A) take a backup BEFORE upgrading and (B) afterwards.
|
||||
Recommended format: KMZ.
|
||||
|
||||
BREAKING CHANGES:
|
||||
- Internal storage of track segments is changed
|
||||
(user generated events are now considered trackpoints without a location but with timestamp)
|
||||
- Device time instead of GPS time is stored
|
||||
(existing data is not modified)
|
||||
- Dashboard API v2
|
||||
(please upgrade OSMDashboard 2.10.0)
|
||||
|
||||
Changes:
|
||||
- KML/KMZ can used to backup and restore exactly (incl. statistics)
|
||||
|
||||
Developer:
|
||||
- Reworked track segmentation and it's storage (formerly PAUSE/RESUME marker)
|
||||
- TrackStatistics are computed solely using TrackPoints
|
||||
- Replaced long with Instant/Duration
|
||||
@@ -0,0 +1,7 @@
|
||||
v3.15.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- After recording show sensor statistics (e.g., avg HR)
|
||||
|
||||
Bugfix:
|
||||
- Crash on recording start with show latitude/longitude enabled
|
||||
@@ -0,0 +1,7 @@
|
||||
v3.15.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Updated translations
|
||||
|
||||
Bugfix:
|
||||
- Crash on GPX export
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.15.3: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Computation avg/max: heart rate, cadence, and power
|
||||
|
||||
Bugfix:
|
||||
- KML/KMZ do not export #start/#end placemarks anymore
|
||||
- While recording: Speed/location was sometimes falsly shown as 0.
|
||||
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 574 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 456 KiB |
|
After Width: | Height: | Size: 630 KiB |
|
After Width: | Height: | Size: 416 KiB |
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 798 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 862 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 338 KiB |
|
After Width: | Height: | Size: 665 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 661 KiB |
|
After Width: | Height: | Size: 1001 KiB |
@@ -0,0 +1,168 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.action.MotionEvents;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoUITest {
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant("android.permission.ACCESS_FINE_LOCATION");
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void record_pause_resume_stop() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(5000));
|
||||
|
||||
//pause
|
||||
trackControllerRecordButton.perform(veryLongTouch(1600));
|
||||
|
||||
// wait; stay paused
|
||||
trackControllerRecordButton.perform(waitFor(1000));
|
||||
|
||||
// resume
|
||||
trackControllerRecordButton.perform(click());
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(1000));
|
||||
|
||||
// stop;
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
}
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void record_move_through_tabs() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.track_controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_controller_stop));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(1));
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(2));
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(3));
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(0));
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
// stop
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
}
|
||||
}
|
||||
|
||||
private static ViewAction veryLongTouch(final int duration_ms) {
|
||||
return new ViewAction() {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Perform long touch.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, final View view) {
|
||||
// Get view absolute position
|
||||
int[] location = new int[2];
|
||||
view.getLocationOnScreen(location);
|
||||
|
||||
// Offset coordinates by view position
|
||||
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
|
||||
|
||||
// Send down event, pause, and send up
|
||||
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
|
||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||
MotionEvents.sendUp(uiController, down, coordinates);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ViewAction waitFor(final long duration_ms) {
|
||||
return new ViewAction() {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Wait for milliseconds.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, final View view) {
|
||||
uiController.loopMainThreadForAtLeast(duration_ms);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ViewAction selectTabAtIndex(final int index) {
|
||||
return new ViewAction() {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Selecting tab.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isDisplayed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, View view) {
|
||||
TabLayout tabLayout = (TabLayout) view;
|
||||
tabLayout.getTabAt(index).select();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,9 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -40,7 +43,7 @@ import static org.junit.Assert.assertEquals;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DescriptionGeneratorTest {
|
||||
|
||||
private static final long START_TIME = 1288721514000L;
|
||||
private static final Instant START_TIME = Instant.ofEpochMilli(1288721514000L);
|
||||
private DescriptionGenerator descriptionGenerator;
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
@@ -58,13 +61,14 @@ public class DescriptionGeneratorTest {
|
||||
Track track = new Track();
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setMaxElevation(550);
|
||||
stats.setMinElevation(-500);
|
||||
stats.setTotalElevationGain(6000);
|
||||
stats.setStartTime_ms(START_TIME);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
stats.setTotalElevationLoss(6000f);
|
||||
stats.setStartTime(START_TIME);
|
||||
track.setTrackStatistics(stats);
|
||||
track.setCategory("hiking");
|
||||
String expected = //"Created by"
|
||||
@@ -84,6 +88,7 @@ public class DescriptionGeneratorTest {
|
||||
+ "Max elevation: 550 m (1804 ft)<br>"
|
||||
+ "Min elevation: -500 m (-1640 ft)<br>"
|
||||
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
||||
+ "Elevation loss: 6000 m (19685 ft)<br>"
|
||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||
|
||||
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||
@@ -94,13 +99,14 @@ public class DescriptionGeneratorTest {
|
||||
Track track = new Track();
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setMaxElevation(Double.POSITIVE_INFINITY);
|
||||
stats.setMinElevation(Double.NEGATIVE_INFINITY);
|
||||
stats.setTotalElevationGain(6000);
|
||||
stats.setStartTime_ms(START_TIME);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
stats.setTotalElevationLoss(6000f);
|
||||
stats.setStartTime(START_TIME);
|
||||
track.setTrackStatistics(stats);
|
||||
track.setCategory("hiking");
|
||||
String expected = //"Created by"
|
||||
@@ -118,6 +124,7 @@ public class DescriptionGeneratorTest {
|
||||
+ "Average moving pace: 0:15 min/km (0:24 min/mi)<br>"
|
||||
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
|
||||
+ "Elevation gain: 6000 m (19685 ft)<br>"
|
||||
+ "Elevation loss: 6000 m (19685 ft)<br>"
|
||||
+ "Recorded: " + StringUtils.formatDateTime(context, START_TIME) + "<br>";
|
||||
|
||||
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
|
||||
@@ -135,12 +142,12 @@ public class DescriptionGeneratorTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DescriptionGenerator#writeTime(long, StringBuilder, int, String)}.
|
||||
* Tests {@link DescriptionGenerator#writeTime(Duration, StringBuilder, int, String)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWriteTime() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
descriptionGenerator.writeTime(1000, builder, R.string.description_total_time, "<br>");
|
||||
descriptionGenerator.writeTime(Duration.ofMillis(1000), builder, R.string.description_total_time, "<br>");
|
||||
assertEquals("Total time: 00:01<br>", builder.toString());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
@@ -17,6 +19,7 @@ public class TestDataUtil {
|
||||
public static final double INITIAL_LONGITUDE = -57.0;
|
||||
public static final double ALTITUDE_INTERVAL = 2.5;
|
||||
public static final float ELEVATION_GAIN = 3;
|
||||
public static final float ELEVATION_LOSS = 3;
|
||||
|
||||
/**
|
||||
* Create a track without any trackPoints.
|
||||
@@ -35,19 +38,65 @@ public class TestDataUtil {
|
||||
* @param trackId the trackId of the track
|
||||
* @param numPoints the trackPoints number in the track
|
||||
*/
|
||||
public static Pair<Track, TrackPoint[]> createTrack(Track.Id trackId, int numPoints) {
|
||||
@Deprecated //TODO Should start with SEGMENT_START_MANUAL and end with SEGMENT_END_MANUAL.
|
||||
public static Pair<Track, List<TrackPoint>> createTrack(Track.Id trackId, int numPoints) {
|
||||
Track track = createTrack(trackId);
|
||||
|
||||
TrackPoint[] trackPoints = new TrackPoint[numPoints];
|
||||
List<TrackPoint> trackPoints = new ArrayList<>(numPoints);
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
trackPoints[i] = createTrackPoint(i);
|
||||
trackPoints.add(createTrackPoint(i));
|
||||
}
|
||||
|
||||
return new Pair<>(track, trackPoints);
|
||||
}
|
||||
|
||||
|
||||
public static TrackData createTestingTrack(Track.Id trackId) {
|
||||
Track track = createTrack(trackId);
|
||||
|
||||
int i = 0;
|
||||
List<TrackPoint> trackPoints = List.of(
|
||||
TrackPoint.createSegmentStartManualWithTime(Instant.ofEpochMilli(i++ + 1)),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++, TrackPoint.Type.SEGMENT_START_AUTOMATIC),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
TrackPoint.createSegmentEndWithTime(Instant.ofEpochSecond(i++ + 1)),
|
||||
|
||||
TrackPoint.createSegmentStartManualWithTime(Instant.ofEpochSecond(i++)),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
createTrackPoint(i++),
|
||||
TrackPoint.createSegmentEndWithTime(Instant.ofEpochSecond(i++ + 1))
|
||||
);
|
||||
|
||||
List<Marker> markers = List.of(
|
||||
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, 0.0, 0, trackPoints.get(1), null),
|
||||
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, 0.0, 0, trackPoints.get(4), null),
|
||||
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, 0.0, 0, trackPoints.get(5), null)
|
||||
);
|
||||
|
||||
return new TrackData(track, trackPoints, markers);
|
||||
}
|
||||
|
||||
public static class TrackData {
|
||||
public final Track track;
|
||||
public final List<TrackPoint> trackPoints;
|
||||
public final List<Marker> markers;
|
||||
|
||||
public TrackData(Track track, List<TrackPoint> trackPoints, List<Marker> markers) {
|
||||
this.track = track;
|
||||
this.trackPoints = trackPoints;
|
||||
this.markers = markers;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, Track.Id trackId, int numPoints) {
|
||||
Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints);
|
||||
Pair<Track, List<TrackPoint>> pair = createTrack(trackId, numPoints);
|
||||
|
||||
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
|
||||
|
||||
@@ -60,18 +109,25 @@ public class TestDataUtil {
|
||||
* @param i the index for the TrackPoint.
|
||||
*/
|
||||
public static TrackPoint createTrackPoint(int i) {
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||
trackPoint.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
trackPoint.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
trackPoint.setAccuracy((float) i / 100.0f);
|
||||
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
trackPoint.setTime(i + 1);
|
||||
trackPoint.setSpeed(5f + (i / 10));
|
||||
trackPoint.setTime(Instant.ofEpochSecond(i + 1));
|
||||
trackPoint.setSpeed(5f + (i / 10f));
|
||||
|
||||
trackPoint.setHeartRate_bpm(100f + i);
|
||||
trackPoint.setCyclingCadence_rpm(300f + i);
|
||||
trackPoint.setPower(400f + i);
|
||||
trackPoint.setElevationGain(ELEVATION_GAIN);
|
||||
trackPoint.setElevationLoss(ELEVATION_LOSS);
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
public static TrackPoint createTrackPoint(int i, TrackPoint.Type type) {
|
||||
TrackPoint trackPoint = createTrackPoint(i);
|
||||
trackPoint.setType(type);
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
@@ -81,25 +137,17 @@ public class TestDataUtil {
|
||||
* @param track track to be inserted
|
||||
* @param trackPoints trackPoints to be inserted
|
||||
*/
|
||||
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, TrackPoint[] trackPoints) {
|
||||
public static void insertTrackWithLocations(ContentProviderUtils contentProviderUtils, Track track, List<TrackPoint> trackPoints) {
|
||||
contentProviderUtils.insertTrack(track);
|
||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, track.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Waypoint with a photo.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param trackId The track id.
|
||||
* @param location The location.
|
||||
* @return the Waypoint created.
|
||||
*/
|
||||
public static Marker createWaypointWithPhoto(Context context, Track.Id trackId, Location location) throws IOException {
|
||||
public static Marker createMarkerWithPhoto(Context context, Track.Id trackId, TrackPoint trackPoint) throws IOException {
|
||||
File dstFile = new File(FileUtils.getImageUrl(context, trackId));
|
||||
dstFile.createNewFile();
|
||||
Uri photoUri = FileUtils.getUriForFile(context, dstFile);
|
||||
String photoUrl = photoUri.toString();
|
||||
|
||||
return new Marker("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl);
|
||||
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, 0.0, 0, trackPoint, photoUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TestSensorDataUtil {
|
||||
|
||||
private List<TrackPoint> trackPointList = new ArrayList<>();
|
||||
private List<SensorData> sensorDataList = new ArrayList<>();
|
||||
|
||||
public void add(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
|
||||
sensorDataList.add(new TestSensorDataUtil.SensorData(time, hr, cadence, power, type));
|
||||
TrackPoint tp = new TrackPoint(type);
|
||||
int i = trackPointList.size() + 1;
|
||||
tp.setLatitude(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
tp.setLongitude(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
tp.setHeartRate_bpm(hr);
|
||||
tp.setCyclingCadence_rpm(cadence);
|
||||
tp.setPower(power);
|
||||
tp.setAccuracy(1f);
|
||||
tp.setAltitude(1f);
|
||||
tp.setTime(time);
|
||||
tp.setSpeed(5f + (i / 10f));
|
||||
tp.setElevationGain(3f);
|
||||
tp.setElevationLoss(3f);
|
||||
trackPointList.add(tp);
|
||||
}
|
||||
|
||||
public List<TrackPoint> getTrackPointList() {
|
||||
return this.trackPointList;
|
||||
}
|
||||
|
||||
public SensorDataStats computeStats() {
|
||||
if (sensorDataList == null || sensorDataList.size() <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SensorDataStats stats = new SensorDataStats();
|
||||
long timeElapsed;
|
||||
long movingTime = 0;
|
||||
SensorData dataPrev = sensorDataList.get(0);
|
||||
stats.maxHr = dataPrev.hr;
|
||||
stats.maxCadence = dataPrev.cadence;
|
||||
SensorData dataCurrent;
|
||||
for (int i = 1; i < sensorDataList.size(); i++) {
|
||||
dataCurrent = sensorDataList.get(i);
|
||||
if (dataPrev.type != TrackPoint.Type.SEGMENT_START_MANUAL) {
|
||||
timeElapsed = dataCurrent.type != TrackPoint.Type.SEGMENT_START_MANUAL ? dataCurrent.time.getEpochSecond() - dataPrev.time.getEpochSecond() : 0;
|
||||
stats.avgHr += (dataPrev.hr * timeElapsed);
|
||||
stats.maxHr = dataPrev.hr > stats.maxHr ? dataPrev.hr : stats.maxHr;
|
||||
stats.avgCadence += (dataPrev.cadence * timeElapsed);
|
||||
stats.maxCadence = dataPrev.cadence > stats.maxCadence ? dataPrev.cadence : stats.maxCadence;
|
||||
stats.avgPower += (dataPrev.power * timeElapsed);
|
||||
|
||||
movingTime += timeElapsed;
|
||||
}
|
||||
dataPrev = dataCurrent;
|
||||
}
|
||||
|
||||
stats.avgHr /= movingTime;
|
||||
stats.avgCadence /= movingTime;
|
||||
stats.avgPower /= movingTime;
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private static class SensorData {
|
||||
Instant time;
|
||||
float hr;
|
||||
float cadence;
|
||||
float power;
|
||||
TrackPoint.Type type;
|
||||
|
||||
public SensorData(Instant time, Float hr, Float cadence, Float power, TrackPoint.Type type) {
|
||||
this.time = time;
|
||||
this.hr = hr == null ? 0 : hr;
|
||||
this.cadence = cadence == null ? 0 : cadence;
|
||||
this.power = power == null ? 0 : power;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorDataStats {
|
||||
public float avgHr = 0f;
|
||||
public float maxHr = 0f;
|
||||
public float avgCadence = 0f;
|
||||
public float maxCadence = 0f;
|
||||
public float avgPower = 0f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TrackPointTest {
|
||||
|
||||
@Test
|
||||
public void isRecent_true() {
|
||||
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
tp.setTime(Instant.now());
|
||||
|
||||
assertTrue(tp.isRecent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isRecent_false() {
|
||||
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
tp.setTime(Instant.now().minus(2, ChronoUnit.MINUTES));
|
||||
|
||||
assertFalse(tp.isRecent());
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -14,7 +15,9 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
@@ -165,6 +168,101 @@ public class CustomSQLiteOpenHelperTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void upgrade_data_to_30() {
|
||||
// given: a track in version 29
|
||||
createVersion23();
|
||||
try (SQLiteDatabase db29 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 29).getWritableDatabase()) {
|
||||
db29.beginTransaction();
|
||||
db29.execSQL("INSERT INTO tracks (_id) VALUES (1)");
|
||||
|
||||
// Record -> stop
|
||||
db29.execSQL("INSERT INTO tracks (_id) VALUES (2)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.1, 2.1)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.2, 2.2)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (2, 2.3, 2.3)");
|
||||
|
||||
// Record -> pause -> stop
|
||||
db29.execSQL("INSERT INTO tracks (_id) VALUES (3)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.1, 3.1)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 3.2, 3.2)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (3, 0.0, 100)"); //manual PAUSE
|
||||
|
||||
// Record -> segment marker (distance to previous) -> pause -> resume -> stop
|
||||
db29.execSQL("INSERT INTO tracks (_id) VALUES (4)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.1, 4.1)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.2, 4.2)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //SegmentEndMarker; will be deleted
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.3, 4.3)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.4, 4.4)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 100 * 1E6)"); //manual PAUSE
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 0.0, 200 * 1E6)"); //manual RESUME
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.5, 4.5)");
|
||||
db29.execSQL("INSERT INTO trackpoints (trackId, longitude, latitude) VALUES (4, 4.6, 4.6)");
|
||||
|
||||
db29.setTransactionSuccessful();
|
||||
db29.endTransaction();
|
||||
}
|
||||
|
||||
// when / then
|
||||
try (SQLiteDatabase db30 = new CustomSQLiteOpenHelper(context, DATABASE_NAME, 30).getWritableDatabase()) {
|
||||
{
|
||||
// Track 1
|
||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
queryBuilder.setTables("trackpoints");
|
||||
queryBuilder.appendWhere("trackid = 1");
|
||||
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
|
||||
assertEquals(0, cursor.getCount());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Track 2
|
||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
queryBuilder.setTables("trackpoints");
|
||||
queryBuilder.appendWhere("trackid = 2");
|
||||
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
|
||||
assertEquals(3, cursor.getCount());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Track 3
|
||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
queryBuilder.setTables("trackpoints");
|
||||
queryBuilder.appendWhere("trackid = 3");
|
||||
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
|
||||
assertEquals(3, cursor.getCount());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Track 4
|
||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
queryBuilder.setTables("trackpoints");
|
||||
queryBuilder.appendWhere("trackid = 4");
|
||||
try (Cursor cursor = queryBuilder.query(db30, null, null, null, null, null, "_id")) {
|
||||
assertEquals(8, cursor.getCount());
|
||||
|
||||
List<Integer> types = new ArrayList<>();
|
||||
List<Double> latitude = new ArrayList<>();
|
||||
cursor.moveToFirst();
|
||||
do {
|
||||
types.add(cursor.getInt(cursor.getColumnIndexOrThrow("type")));
|
||||
if (!cursor.isNull(cursor.getColumnIndexOrThrow("latitude"))) {
|
||||
latitude.add(cursor.getDouble(cursor.getColumnIndexOrThrow("latitude")));
|
||||
} else {
|
||||
latitude.add(-1.0);
|
||||
}
|
||||
} while (cursor.moveToNext());
|
||||
|
||||
assertEquals(List.of(0, 0, -1, 0, 1, -2, 0, 0), types);
|
||||
assertEquals(List.of(4.1, 4.2, 4.3, 4.4, -1.0, -1.0, 4.5, 4.6), latitude);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createVersion23() {
|
||||
// Manually create database schema with version 23 (base version)
|
||||
SQLiteDatabase dbBase = new SQLiteOpenHelper(context, DATABASE_NAME, null, 23) {
|
||||
|
||||
@@ -24,7 +24,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(60, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -37,7 +37,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(33.53, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(33.53, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -50,7 +50,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(0, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(0, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertFalse(current.hasCadence_rpm());
|
||||
assertFalse(current.hasValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -77,7 +77,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(60, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -90,7 +90,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(60, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,7 +103,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous, 2150);
|
||||
|
||||
// then
|
||||
assertEquals(1.20, current.getSpeed_mps(), 0.01);
|
||||
assertEquals(1.20, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,7 +116,7 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous, 2000);
|
||||
|
||||
// then
|
||||
assertEquals(2, current.getSpeed_mps(), 0.01);
|
||||
assertEquals(2, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -28,7 +28,9 @@ import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.TrackStubUtils;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -62,9 +64,8 @@ public class ChartFragmentTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
boolean chartByDistance = false;
|
||||
chartFragment = (ChartFragment) ChartFragment.newInstance(chartByDistance);
|
||||
chartFragment = ChartFragment.newInstance(chartByDistance);
|
||||
chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance));
|
||||
chartFragment.setTrackStatisticsUpdater(TrackStubUtils.INITIAL_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,15 +193,21 @@ public class ChartFragmentTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_time() {
|
||||
// given
|
||||
chartFragment.setChartByDistance(false);
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setTime(Instant.ofEpochMilli(TrackStubUtils.INITIAL_TIME)); //Keep old TrackPoint behavior of having time=0 for this test
|
||||
|
||||
// when
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
|
||||
// then
|
||||
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
long timeSpan = 222;
|
||||
Duration timeSpan = Duration.ofMillis(222);
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setTime(trackPoint1.getTime() + timeSpan);
|
||||
trackPoint2.setTime(Instant.ofEpochMilli(TrackStubUtils.INITIAL_TIME).plus(timeSpan));
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals((double) timeSpan, point.getTimeOrDistance(), 0.01);
|
||||
assertEquals(timeSpan, Duration.ofMillis((long) point.getTimeOrDistance()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,8 +258,8 @@ public class ChartFragmentTest {
|
||||
/*
|
||||
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
|
||||
*/
|
||||
trackPoint2.setTime(trackPoint1.getTime() + 222);
|
||||
trackPoint2.setSpeed(130);
|
||||
trackPoint2.setTime(trackPoint1.getTime().plusMillis(222));
|
||||
trackPoint2.setSpeed(130f);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01);
|
||||
}
|
||||
@@ -276,8 +283,8 @@ public class ChartFragmentTest {
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
trackPoint2.setTime(trackPoint2.getTime() + 222);
|
||||
trackPoint2.setSpeed(102);
|
||||
trackPoint2.setTime(trackPoint2.getTime().plusMillis(222));
|
||||
trackPoint2.setSpeed(102f);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
|
||||
}
|
||||
@@ -301,8 +308,8 @@ public class ChartFragmentTest {
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
trackPoint2.setTime(trackPoint2.getTime() + 222);
|
||||
trackPoint2.setSpeed(102);
|
||||
trackPoint2.setTime(trackPoint2.getTime().plusMillis(222));
|
||||
trackPoint2.setSpeed(102f);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01);
|
||||
}
|
||||
@@ -314,7 +321,7 @@ public class ChartFragmentTest {
|
||||
public void testCreatePendingPoint_pace_zeroSpeed() {
|
||||
chartFragment.setReportSpeed(false);
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setSpeed(0);
|
||||
trackPoint.setSpeed(0f);
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
assertEquals(0.0, point.getPace(), 0.01);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
@@ -57,7 +57,7 @@ public class TrackStubUtils {
|
||||
location.setAltitude(altitude);
|
||||
location.setAccuracy(INITIAL_ACCURACY);
|
||||
location.setSpeed(INITIAL_SPEED);
|
||||
location.setTime(INITIAL_TIME);
|
||||
location.setTime(INITIAL_TIME); //TODO This is nowadays ignored as the constructor will replace the time.
|
||||
location.setBearing(INITIAL_BEARING);
|
||||
|
||||
return new TrackPoint(location);
|
||||
@@ -0,0 +1,53 @@
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class KmlTrackExporterTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
/**
|
||||
* Sensor data by type should only be created if present in at least on TrackPoint.
|
||||
*/
|
||||
@Test
|
||||
public void writeCloseSegment_only_write_sensordata_if_present() {
|
||||
String expected = "<when>1970-01-01T00:00:00Z</when>\n" +
|
||||
"<gx:coord/>\n" +
|
||||
"<ExtendedData>\n" +
|
||||
"<SchemaData schemaUrl=\"#schema\">\n" +
|
||||
"</SchemaData>\n" +
|
||||
"</ExtendedData>\n" +
|
||||
"</gx:Track>\n";
|
||||
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0));
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
KMLTrackExporter kmlTrackWriter = (KMLTrackExporter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
||||
kmlTrackWriter.prepare(outputStream);
|
||||
|
||||
kmlTrackWriter.writeTrackPoint(trackPoint);
|
||||
|
||||
// when
|
||||
kmlTrackWriter.writeCloseSegment();
|
||||
kmlTrackWriter.close();
|
||||
|
||||
// then
|
||||
assertEquals(expected, outputStream.toString());
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.ZoneOffset;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Abstract class for testing file track importers.
|
||||
*
|
||||
* @author Jimmy Shih.
|
||||
*/
|
||||
public abstract class AbstractTestFileTrackImporter {
|
||||
|
||||
static final String TRACK_NAME_0 = "blablub";
|
||||
static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
|
||||
|
||||
static final double TRACK_LATITUDE = 48.768364;
|
||||
static final double TRACK_LONGITUDE = 9.177886;
|
||||
static final double TRACK_ELEVATION = 324.0;
|
||||
|
||||
static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
|
||||
static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123Z";
|
||||
static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123Z";
|
||||
static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123Z";
|
||||
|
||||
static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
|
||||
static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
|
||||
|
||||
static final long TRACK_POINT_ID_0 = 1;
|
||||
static final long TRACK_POINT_ID_1 = 2;
|
||||
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(MarkerColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
|
||||
|
||||
protected final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
static {
|
||||
// We can't omit the timezones in the test, otherwise it'll use the local timezone and fail depending on where the test runner is.
|
||||
TimeZone utc = SimpleTimeZone.getTimeZone(ZoneOffset.UTC);
|
||||
DATE_FORMAT_0.setTimeZone(utc);
|
||||
DATE_FORMAT_1.setTimeZone(utc);
|
||||
}
|
||||
|
||||
@Mock
|
||||
public ContentProviderUtils contentProviderUtils;
|
||||
|
||||
TrackPoint createTrackPoint(int index, long time) {
|
||||
Location trackPoint = new Location(LocationManager.GPS_PROVIDER);
|
||||
trackPoint.setLatitude(TRACK_LATITUDE + index);
|
||||
trackPoint.setLongitude(TRACK_LONGITUDE + index);
|
||||
trackPoint.setAltitude(TRACK_ELEVATION + index);
|
||||
trackPoint.setTime(time);
|
||||
return new TrackPoint(trackPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects the first track point to be added.
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
* @param trackId the track id
|
||||
* @param trackPointId the track point id
|
||||
*/
|
||||
protected void expectFirstTrackPoint(TrackPoint trackPoint, Track.Id trackId, long trackPointId) {
|
||||
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(trackId))).thenReturn(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects the track to be updated.
|
||||
*
|
||||
* @param trackCaptor the track
|
||||
* @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, Track.Id trackId) {
|
||||
contentProviderUtils.updateTrack(trackCaptor.capture());
|
||||
|
||||
when(contentProviderUtils.insertMarker(any())).thenReturn(WAYPOINT_ID_O_URI);
|
||||
if (lastTrack) {
|
||||
// Return null to not add waypoints
|
||||
when(contentProviderUtils.getTrack(trackId)).thenReturn(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected void verifyTrack(Track track, String name, String description, long time) {
|
||||
assertEquals(name, track.getName());
|
||||
assertEquals(description, track.getDescription());
|
||||
if (time != -1L) {
|
||||
assertEquals(time, track.getTrackStatistics().getStartTime_ms());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
@@ -18,34 +24,50 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||
* <p>
|
||||
* TODO: test ignores {@link TrackStatistics} for now.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class ExportImportTest {
|
||||
|
||||
private static final String TAG = ExportImportTest.class.getSimpleName();
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@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 final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
@@ -54,37 +76,43 @@ public class ExportImportTest {
|
||||
private static final String TRACK_CATEGORY = "the category";
|
||||
private static final String TRACK_DESCRIPTION = "the description";
|
||||
|
||||
private final List<Marker> markers = new ArrayList<>();
|
||||
private final List<TrackPoint> trackPoints = new ArrayList<>();
|
||||
private Track track;
|
||||
private List<Marker> markers = new ArrayList<>();
|
||||
private List<TrackPoint> trackPoints = new ArrayList<>();
|
||||
|
||||
private Track.Id trackId;
|
||||
private Track.Id importTrackId;
|
||||
private final Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
|
||||
track.first.setIcon(TRACK_ICON);
|
||||
track.first.setCategory(TRACK_CATEGORY);
|
||||
track.first.setDescription(TRACK_DESCRIPTION);
|
||||
contentProviderUtils.insertTrack(track.first);
|
||||
contentProviderUtils.bulkInsertTrackPoint(track.second, track.first.getId());
|
||||
public void setUp() throws TimeoutException {
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)));
|
||||
|
||||
trackPoints.clear();
|
||||
trackPoints.addAll(Arrays.asList(track.second));
|
||||
trackId = service.startNewTrack();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Marker marker = new Marker(trackId, track.second[i].getLocation());
|
||||
marker.setName("the marker " + i);
|
||||
marker.setDescription("the marker description " + i);
|
||||
marker.setCategory("the marker category" + i);
|
||||
marker.setIcon("the marker icon" + i);
|
||||
marker.setPhotoUrl("the photo url" + i);
|
||||
contentProviderUtils.insertMarker(marker);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
markers.add(marker);
|
||||
}
|
||||
service.resumeCurrentTrack();
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50), 0);
|
||||
service.endCurrentTrack();
|
||||
|
||||
assertEquals(markers.size(), contentProviderUtils.getMarkerCount(trackId));
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
track.setIcon(TRACK_ICON);
|
||||
track.setCategory(TRACK_CATEGORY);
|
||||
track.setDescription(TRACK_DESCRIPTION);
|
||||
contentProviderUtils.updateTrack(track);
|
||||
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
markers = contentProviderUtils.getMarkers(trackId);
|
||||
assertEquals(10, trackPoints.size());
|
||||
assertEquals(2, markers.size());
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -95,6 +123,7 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("Not implemented")
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_only_track() {
|
||||
@@ -108,7 +137,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context);
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -131,11 +160,14 @@ public class ExportImportTest {
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
assertEquals(track.getUuid(), importedTrack.getUuid());
|
||||
|
||||
// 2. markers
|
||||
assertMarkers();
|
||||
// 2. trackpoints
|
||||
assertTrackpoints(trackPoints, false, false, false, false, false);
|
||||
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(false, false, false, false);
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(false, false);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@@ -144,7 +176,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context);
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -166,11 +198,14 @@ public class ExportImportTest {
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
|
||||
// 2. markers
|
||||
assertMarkers();
|
||||
// 2. trackpoints
|
||||
assertTrackpoints(trackPoints, true, true, true, true, true);
|
||||
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(true, true, true, true);
|
||||
// 2. trackstatistics
|
||||
assertTrackStatistics(false, true);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@@ -180,7 +215,7 @@ 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);
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -235,7 +270,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -259,11 +294,20 @@ public class ExportImportTest {
|
||||
//TODO exporting and importing a track icon is not yet supported by GpxTrackWriter.
|
||||
//assertEquals(track.getIcon(), trackImported.getIcon());
|
||||
|
||||
// 2. markers
|
||||
assertMarkers();
|
||||
// 2. trackpoints
|
||||
// The GPX exporter does not support exporting TrackPoints without lat/lng.
|
||||
// Therefore, the track segmentation is changes.
|
||||
List<TrackPoint> trackPointsWithCoordinates = trackPoints.stream().filter(it -> TrackPoint.Type.SEGMENT_START_AUTOMATIC.equals(it.getType()) || TrackPoint.Type.TRACKPOINT.equals(it.getType())).collect(Collectors.toList());
|
||||
trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
trackPointsWithCoordinates.get(3).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(false, true, true, false);
|
||||
assertTrackpoints(trackPointsWithCoordinates, false, true, true, true, true);
|
||||
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(true, true);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@@ -273,7 +317,7 @@ public class ExportImportTest {
|
||||
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -310,23 +354,42 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackpoints(boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain) {
|
||||
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss) {
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
|
||||
assertEquals(trackPoints.size(), importedTrackPoints.size());
|
||||
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
TrackPoint trackPoint = trackPoints.get(i);
|
||||
TrackPoint importedTrackPoint = importedTrackPoints.get(i);
|
||||
|
||||
assertEquals(trackPoint.getTime(), importedTrackPoint.getTime(), 0.01);
|
||||
assertEquals(trackPoint.getTime(), importedTrackPoint.getTime());
|
||||
TrackPoint.Type type = trackPoint.getType();
|
||||
Log.e(TAG, "" + importedTrackPoint.getType().equals(type));
|
||||
assertEquals("" + i, type, importedTrackPoint.getType());
|
||||
|
||||
// TODO Not exported for GPX/KML
|
||||
// assertEquals(trackPoint.getAccuracy(), importedTrackPoint.getAccuracy(), 0.01);
|
||||
|
||||
assertEquals(trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
|
||||
assertEquals(trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
|
||||
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
|
||||
assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.001);
|
||||
assertEquals(trackPoint.hasLocation(), importedTrackPoint.hasLocation());
|
||||
if (trackPoint.hasLocation()) {
|
||||
assertEquals(trackPoint.getLatitude(), importedTrackPoint.getLatitude(), 0.001);
|
||||
assertEquals(trackPoint.getLongitude(), importedTrackPoint.getLongitude(), 0.001);
|
||||
}
|
||||
assertEquals(trackPoint.hasSpeed(), importedTrackPoint.hasSpeed());
|
||||
if (trackPoint.hasSpeed()) {
|
||||
assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.001);
|
||||
}
|
||||
assertEquals(trackPoint.hasAltitude(), importedTrackPoint.hasAltitude());
|
||||
if (trackPoint.hasAltitude()) {
|
||||
assertEquals(trackPoint.getAltitude(), importedTrackPoint.getAltitude(), 0.001);
|
||||
}
|
||||
|
||||
if (type.equals(TrackPoint.Type.SEGMENT_START_MANUAL) || type.equals(TrackPoint.Type.SEGMENT_END_MANUAL)) {
|
||||
//TODO REMOVE
|
||||
continue;
|
||||
}
|
||||
|
||||
if (verifyHeartrate) {
|
||||
assertEquals(trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
}
|
||||
@@ -339,6 +402,64 @@ public class ExportImportTest {
|
||||
if (verifyElevationGain) {
|
||||
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
|
||||
}
|
||||
if (verifyElevationLoss) {
|
||||
assertEquals(trackPoint.getElevationLoss(), importedTrackPoint.getElevationLoss(), 0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss) {
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
|
||||
assertNotNull(importedTrack.getTrackStatistics());
|
||||
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
|
||||
|
||||
// Time
|
||||
assertTrue(trackStatistics.getStartTime().isBefore(trackStatistics.getStopTime())); //Just to be sure.
|
||||
if (!isGpx) {
|
||||
assertEquals(trackStatistics.getStartTime(), importedTrackStatistics.getStartTime());
|
||||
assertEquals(trackStatistics.getStopTime(), importedTrackStatistics.getStopTime());
|
||||
|
||||
assertEquals(trackStatistics.getTotalTime(), importedTrackStatistics.getTotalTime());
|
||||
assertEquals(trackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime());
|
||||
|
||||
// Distance
|
||||
assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01);
|
||||
|
||||
// Speed
|
||||
assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed(), 0.01);
|
||||
assertEquals(trackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed(), 0.01);
|
||||
assertEquals(trackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed(), 0.01);
|
||||
}
|
||||
|
||||
// Elevation
|
||||
assertEquals(trackStatistics.getMinElevation(), importedTrackStatistics.getMinElevation(), 0.01);
|
||||
assertEquals(trackStatistics.getMaxElevation(), importedTrackStatistics.getMaxElevation(), 0.01);
|
||||
if (verifyElevationGainAndLoss) {
|
||||
assertEquals(trackStatistics.getTotalElevationGain(), importedTrackStatistics.getTotalElevationGain(), 0.01);
|
||||
assertEquals(trackStatistics.getTotalElevationLoss(), importedTrackStatistics.getTotalElevationLoss(), 0.01);
|
||||
} else {
|
||||
assertFalse(importedTrackStatistics.hasTotalElevationGain());
|
||||
assertFalse(importedTrackStatistics.hasTotalElevationLoss());
|
||||
}
|
||||
}
|
||||
|
||||
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, long speed, long altitude, float elevationGain, float heartRate, float cyclingCadence, float power) {
|
||||
Location location = new Location("");
|
||||
location.setTime(time);
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
location.setAccuracy(accuracy);
|
||||
location.setAltitude(altitude);
|
||||
location.setSpeed(speed);
|
||||
|
||||
TrackPoint tp = new TrackPoint(location);
|
||||
tp.setHeartRate_bpm(heartRate);
|
||||
tp.setCyclingCadence_rpm(cyclingCadence);
|
||||
tp.setPower(power);
|
||||
tp.setElevationGain(elevationGain);
|
||||
return tp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* Test that legacy KML/GPX formats can still be imported.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class LegacyImportTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
private Track.Id importTrackId;
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (importTrackId != null) {
|
||||
contentProviderUtils.deleteTrack(context, importTrackId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that data with statistics markers is imported and those ignored.
|
||||
* Statistics marker were created to avoid recomputing the track statistics (e.g., distance until a certain time).
|
||||
*/
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_with_statistics_marker() {
|
||||
// given
|
||||
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_statistics_marker);
|
||||
|
||||
// when
|
||||
// 1. import
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("unknown", importedTrack.getCategory());
|
||||
assertEquals("Test Track", importedTrack.getDescription());
|
||||
assertEquals("2020-11-28 18:06", importedTrack.getName());
|
||||
assertEquals("UNKNOWN", importedTrack.getIcon());
|
||||
|
||||
// 2. markers
|
||||
assertEquals(0, contentProviderUtils.getMarkerCount(importTrackId));
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
assertEquals(6, importedTrackPoints.size());
|
||||
|
||||
// first 3 trackpoints
|
||||
assertTrackpoint(importedTrackPoints.get(0), TrackPoint.Type.SEGMENT_START_AUTOMATIC, "2020-11-28T17:06:22.401Z", 1.234156, 12.340097, 469.286376953125);
|
||||
assertTrackpoint(importedTrackPoints.get(1), TrackPoint.Type.TRACKPOINT, "2020-11-28T17:06:25.448Z", 1.23415, 12.340036, 439.1626281738281);
|
||||
assertTrackpoint(importedTrackPoints.get(2), TrackPoint.Type.TRACKPOINT, "2020-11-28T17:06:47.888Z", 1.23405, 12.340057, 421.8070983886719);
|
||||
|
||||
// created resume trackpoint with time of next valid trackpoint
|
||||
assertTrackpoint(importedTrackPoints.get(3), TrackPoint.Type.SEGMENT_START_AUTOMATIC, "2020-11-28T17:06:55.861Z", 1.23405, 12.340057, 419.93902587890625);
|
||||
assertTrackpoint(importedTrackPoints.get(4), TrackPoint.Type.TRACKPOINT, "2020-11-28T17:06:56.905Z", 1.23405, 12.340057, 419.9036560058594);
|
||||
assertTrackpoint(importedTrackPoints.get(5), TrackPoint.Type.TRACKPOINT, "2020-11-28T17:07:20.870Z", 1.234046, 12.340082, 417.99432373046875);
|
||||
}
|
||||
|
||||
/**
|
||||
* At least one valid location is required.
|
||||
* Before v3.15.0, Tracks without TrackPoints could be created; such tracks cannot be imported as we cannot restore the TrackStatistics (especially startTime and stopTime).
|
||||
*/
|
||||
@LargeTest
|
||||
@Test(expected = ImportParserException.class)
|
||||
public void kml_without_locations() {
|
||||
// given
|
||||
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml_empty);
|
||||
|
||||
// when
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that data that contains pause (lat=100, lng=0) and resume (lat=200, lng=0) locations are restored to a segment break.
|
||||
*/
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx_with_pause_resume() {
|
||||
// given
|
||||
GpxFileTrackImporter trackImporter = new GpxFileTrackImporter(context);
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_gpx_pause_resume);
|
||||
|
||||
// when
|
||||
// 1. import
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("the category", importedTrack.getCategory());
|
||||
assertEquals("the description", importedTrack.getDescription());
|
||||
assertEquals("2021-01-07 22:51", importedTrack.getName());
|
||||
assertEquals("UNKNOWN", importedTrack.getIcon());
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
assertEquals(6, importedTrackPoints.size());
|
||||
|
||||
// first segment
|
||||
assertTrackpoint(importedTrackPoints.get(0), TrackPoint.Type.SEGMENT_START_AUTOMATIC, "2021-01-07T21:51:59.179Z", 14.0, 3.0, 10.0);
|
||||
assertTrackpoint(importedTrackPoints.get(1), TrackPoint.Type.TRACKPOINT, "2021-01-07T21:52:00.653Z", 14.001, 3.0, 10.0);
|
||||
assertTrackpoint(importedTrackPoints.get(2), TrackPoint.Type.TRACKPOINT, "2021-01-07T21:52:01.010Z", 14.002, 3.0, 10.0);
|
||||
assertTrackpoint(importedTrackPoints.get(3), TrackPoint.Type.SEGMENT_END_MANUAL, "2021-01-07T21:52:02.658Z", null, null, null);
|
||||
|
||||
// created resume trackpoint with time of next valid trackpoint
|
||||
assertTrackpoint(importedTrackPoints.get(4), TrackPoint.Type.SEGMENT_START_MANUAL, "2021-01-07T21:52:03.873Z", null, null, null);
|
||||
assertTrackpoint(importedTrackPoints.get(5), TrackPoint.Type.TRACKPOINT, "2021-01-07T21:52:04.103Z", 14.003, 3.0, 10.0);
|
||||
}
|
||||
|
||||
private void assertTrackpoint(final TrackPoint trackPoint, final TrackPoint.Type type, final String when, final Double longitude, final Double latitude, final Double altitude) {
|
||||
assertEquals(StringUtils.parseTime(when), trackPoint.getTime());
|
||||
assertEquals(type, trackPoint.getType());
|
||||
|
||||
if (longitude == null) {
|
||||
assertFalse(trackPoint.hasLocation());
|
||||
} else {
|
||||
assertEquals(latitude, (Double) trackPoint.getLatitude());
|
||||
assertEquals(longitude, (Double) trackPoint.getLongitude());
|
||||
}
|
||||
|
||||
if (altitude == null) {
|
||||
assertFalse(trackPoint.hasAltitude());
|
||||
} else {
|
||||
assertEquals(altitude, (Double) trackPoint.getAltitude());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* Imports a legacy kml file with a statistics marker at the beginning
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class LegacyKmlImportTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
private Track.Id importTrackId;
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (importTrackId != null) {
|
||||
contentProviderUtils.deleteTrack(context, importTrackId);
|
||||
}
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_legacy_track_import() {
|
||||
// given
|
||||
KmlFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_track);
|
||||
|
||||
// when
|
||||
// 1. import
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("unknown", importedTrack.getCategory());
|
||||
assertEquals("Test Track", importedTrack.getDescription());
|
||||
assertEquals("2020-11-28 18:06", importedTrack.getName());
|
||||
assertEquals("UNKNOWN", importedTrack.getIcon());
|
||||
|
||||
// 2. markers
|
||||
assertEquals(0, contentProviderUtils.getMarkerCount(importTrackId));
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
assertEquals(8, importedTrackPoints.size()); // 6 trackpoints + pause and resume
|
||||
|
||||
// first 3 trackpoints
|
||||
assertTrackpoint(importedTrackPoints.get(0), "2020-11-28T17:06:22.401Z", 1.234156, 12.340097, 469.286376953125);
|
||||
assertTrackpoint(importedTrackPoints.get(1), "2020-11-28T17:06:25.448Z", 1.23415, 12.340036, 439.1626281738281);
|
||||
assertTrackpoint(importedTrackPoints.get(2), "2020-11-28T17:06:47.888Z", 1.23405, 12.340057, 421.8070983886719);
|
||||
|
||||
// created pause trackpoint with time of previous valid trackpoint
|
||||
assertTrackpoint(importedTrackPoints.get(3), "2020-11-28T17:06:47.888Z", 0.0, 100, 0.0);
|
||||
// created resume trackpoint with time of next valid trackpoint
|
||||
assertTrackpoint(importedTrackPoints.get(4), "2020-11-28T17:06:55.861Z", 0.0, 200, 0.0);
|
||||
|
||||
// last 3 trackpoints
|
||||
assertTrackpoint(importedTrackPoints.get(5), "2020-11-28T17:06:55.861Z", 1.23405, 12.340057, 419.93902587890625);
|
||||
assertTrackpoint(importedTrackPoints.get(6), "2020-11-28T17:06:56.905Z", 1.23405, 12.340057, 419.9036560058594);
|
||||
assertTrackpoint(importedTrackPoints.get(7), "2020-11-28T17:07:20.870Z", 1.234046, 12.340082, 417.99432373046875);
|
||||
}
|
||||
|
||||
private void assertTrackpoint(final TrackPoint trackPoint, final String when, final double longitude, final double latitude, final double altitude) {
|
||||
assertEquals(StringUtils.parseTime(when), trackPoint.getTime());
|
||||
assertEquals(latitude, trackPoint.getLatitude(), 0.001d);
|
||||
assertEquals(longitude, trackPoint.getLongitude(), 0.001d);
|
||||
assertEquals(altitude, trackPoint.getAltitude(), 0.001d);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,8 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.services.handlers.AdaptiveLocationListenerPolicy;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -32,13 +34,13 @@ import static org.junit.Assert.assertEquals;
|
||||
@RunWith(JUnit4.class)
|
||||
public class AdaptiveLocationListenerPolicyTest {
|
||||
|
||||
private static final long MIN = 1000;
|
||||
private static final long MAX = 3000;
|
||||
private static final Duration MIN = Duration.ofMillis(1000);
|
||||
private static final Duration MAX = Duration.ofMillis(3000);
|
||||
private static final int MIN_DISTANCE = 10;
|
||||
private static final long NEW_IDLE_TIME_BIG = 10000;
|
||||
private static final long NEW_IDLE_TIME_NORMAL = 5000;
|
||||
private static final long NEW_IDLE_TIME_SMALL = 2000;
|
||||
private static final long NEW_IDLE_TIME_LESS_THAN_MIN = 500;
|
||||
private static final Duration NEW_IDLE_TIME_BIG = Duration.ofMillis(10000);
|
||||
private static final Duration NEW_IDLE_TIME_NORMAL = Duration.ofMillis(5000);
|
||||
private static final Duration NEW_IDLE_TIME_SMALL = Duration.ofMillis(2000);
|
||||
private static final Duration NEW_IDLE_TIME_LESS_THAN_MIN = Duration.ofMillis(500);
|
||||
private AdaptiveLocationListenerPolicy adaptiveLocationListenerPolicy;
|
||||
|
||||
@Before
|
||||
@@ -62,7 +64,7 @@ public class AdaptiveLocationListenerPolicyTest {
|
||||
|
||||
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL);
|
||||
// First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the nearest second.
|
||||
assertEquals((NEW_IDLE_TIME_NORMAL / 2 / 1000) * 1000, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
|
||||
assertEquals(NEW_IDLE_TIME_NORMAL.dividedBy(2).getSeconds(), adaptiveLocationListenerPolicy.getDesiredPollingInterval().getSeconds());
|
||||
|
||||
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
|
||||
assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
|
||||
|
||||
@@ -47,7 +47,6 @@ 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.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -138,7 +137,7 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_noTracks() throws Exception {
|
||||
public void testRecording_noTracks() throws TimeoutException {
|
||||
// given
|
||||
List<Track> tracks = contentProviderUtils.getTracks();
|
||||
assertTrue(tracks.isEmpty());
|
||||
@@ -156,9 +155,9 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_oldTracks() throws Exception {
|
||||
public void testRecording_oldTracks() throws TimeoutException {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, false);
|
||||
createDummyTrack(trackId, false);
|
||||
|
||||
// when
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
@@ -170,9 +169,9 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_serviceRestart_whileRecording() throws Exception {
|
||||
public void testRecording_serviceRestart_whileRecording() throws TimeoutException {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
createDummyTrack(trackId, true);
|
||||
|
||||
//when
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
@@ -183,11 +182,44 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_pauseAndResume() throws Exception {
|
||||
public void testRecording_start() throws TimeoutException {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
newTrackPoint(service);
|
||||
|
||||
// when
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
// then
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
|
||||
assertEquals(1, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_stop() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
|
||||
assertEquals(2, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_pauseAndResume() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
service.pauseCurrentTrack();
|
||||
@@ -197,29 +229,28 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
//when
|
||||
service.resumeTrack(trackId);
|
||||
newTrackPoint(service);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
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);
|
||||
assertEquals(3, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(2).getType());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_resumeStoppedTrack() throws Exception {
|
||||
public void testRecording_resumeStoppedTrack() throws TimeoutException, InterruptedException {
|
||||
// given
|
||||
createDummyTrack(trackId, -1L, true);
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
newTrackPoint(service);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
service.endCurrentTrack();
|
||||
|
||||
assertEquals(1, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
@@ -231,14 +262,40 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
assertEquals(4, trackPoints.size());
|
||||
assertEquals(TrackPointsColumns.PAUSE_LATITUDE, trackPoints.get(1).getLatitude(), 0.01);
|
||||
assertEquals(TrackPointsColumns.RESUME_LATITUDE, trackPoints.get(2).getLatitude(), 0.01);
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(2).getType());
|
||||
assertEquals(TrackPoint.Type.TRACKPOINT, trackPoints.get(3).getType());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_stopPausedTrack() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
|
||||
// when
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
assertNull(service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
assertEquals(2, trackPoints.size());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_START_MANUAL, trackPoints.get(0).getType());
|
||||
assertEquals(TrackPoint.Type.SEGMENT_END_MANUAL, trackPoints.get(1).getType());
|
||||
}
|
||||
|
||||
@Ignore("Sometimes fails on CI.")
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
||||
public void testRecording_orphanedRecordingTrack() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
|
||||
@@ -254,7 +311,7 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
||||
public void testStartNewTrack_alreadyRecording() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
@@ -274,7 +331,7 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testEndCurrentTrack_noRecording() throws Exception {
|
||||
public void testEndCurrentTrack_noRecording() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
assertFalse(service.isRecording());
|
||||
@@ -290,21 +347,21 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
||||
public void testInsertWaypointMarker_noRecordingTrack() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
// when
|
||||
Marker.Id waypointId = service.insertMarker(null, null, null, null);
|
||||
Marker.Id markerId = service.insertMarker(null, null, null, null);
|
||||
|
||||
// then
|
||||
assertNull(waypointId);
|
||||
assertNull(markerId);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||
public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException {
|
||||
// given
|
||||
TrackRecordingServiceInterface service = ((TrackRecordingServiceInterface) mServiceRule.bindService(createStartIntent(context)));
|
||||
service.startNewTrack();
|
||||
@@ -313,11 +370,11 @@ public class TrackRecordingServiceTest {
|
||||
Track.Id trackId = service.getRecordingTrackId();
|
||||
|
||||
// when
|
||||
Marker.Id waypointId = service.insertMarker(null, null, null, null);
|
||||
Marker.Id markerId = service.insertMarker(null, null, null, null);
|
||||
|
||||
// then
|
||||
assertNotEquals(-1L, waypointId);
|
||||
Marker wpt = contentProviderUtils.getMarker(waypointId);
|
||||
assertNotEquals(-1L, markerId);
|
||||
Marker wpt = contentProviderUtils.getMarker(markerId);
|
||||
assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon());
|
||||
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
||||
assertEquals(trackId, wpt.getTrackId());
|
||||
@@ -328,7 +385,7 @@ public class TrackRecordingServiceTest {
|
||||
}
|
||||
|
||||
private void addTrack(Track track, boolean isRecording) {
|
||||
assertTrue(track.getId().isValid());
|
||||
assertNotNull(track.getId());
|
||||
contentProviderUtils.insertTrack(track);
|
||||
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);
|
||||
@@ -336,12 +393,11 @@ public class TrackRecordingServiceTest {
|
||||
}
|
||||
|
||||
// NOTE: Do not use to create a track that is currently recording.
|
||||
private void createDummyTrack(Track.Id id, long stopTime, boolean isRecording) {
|
||||
private void createDummyTrack(Track.Id id, boolean isRecording) {
|
||||
Track dummyTrack = new Track();
|
||||
dummyTrack.setId(id);
|
||||
dummyTrack.setName("Dummy Track");
|
||||
TrackStatistics trackStatistics = new TrackStatistics();
|
||||
trackStatistics.setStopTime_ms(stopTime);
|
||||
dummyTrack.setTrackStatistics(trackStatistics);
|
||||
addTrack(dummyTrack, isRecording);
|
||||
}
|
||||
@@ -358,7 +414,7 @@ public class TrackRecordingServiceTest {
|
||||
* Inserts a location and waits for 200ms.
|
||||
*/
|
||||
private static void newTrackPoint(TrackRecordingServiceInterface trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) throws InterruptedException {
|
||||
Location location = new Location("gps");
|
||||
Location location = new Location("");
|
||||
location.setLongitude(longitude);
|
||||
location.setLatitude(latitude);
|
||||
location.setAccuracy(accuracy);
|
||||
@@ -368,8 +424,5 @@ public class TrackRecordingServiceTest {
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext());
|
||||
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
//TODO Needed?
|
||||
Thread.sleep(200);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Looper;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -24,7 +25,6 @@ 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;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||
@@ -34,6 +34,7 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* Tests insert location.
|
||||
@@ -69,6 +70,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
customContentProvider.attachInfo(context, null);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
tearDown();
|
||||
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
@@ -79,11 +81,6 @@ public class TrackRecordingServiceTestLocation {
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// Reset service (if some previous test failed)
|
||||
if (service.isRecording() || service.isPaused()) {
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
// Ensure that the database is empty after every test
|
||||
contentProviderUtils.deleteAllTracks(context);
|
||||
}
|
||||
@@ -95,12 +92,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0001, 35.0, 2, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 3, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0003, 35.0, 4, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0005, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0005, 35.0, 6, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -108,8 +105,17 @@ public class TrackRecordingServiceTestLocation {
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
assertEquals(6, trackPoints.size());
|
||||
assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
|
||||
assertEquals(8, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 2),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 3),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 4),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 5),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -117,14 +123,15 @@ public class TrackRecordingServiceTestLocation {
|
||||
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
|
||||
// given
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertNotNull(trackId);
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000001, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000001, 35.0, 2, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 3, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 4, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000005, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000005, 35.0, 6, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -132,8 +139,13 @@ public class TrackRecordingServiceTestLocation {
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
assertEquals(2, trackPoints.size());
|
||||
assertEquals(45.000005, trackPoints.get(1).getLatitude(), 0.01);
|
||||
assertEquals(4, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
// @MediumTest
|
||||
@@ -174,10 +186,13 @@ public class TrackRecordingServiceTestLocation {
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
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);
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 2),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -200,14 +215,17 @@ public class TrackRecordingServiceTestLocation {
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
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);
|
||||
assertEquals(6, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 2),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 5), //TODO Check why this trackPoint is inserted.
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
|
||||
@@ -230,12 +248,12 @@ public class TrackRecordingServiceTestLocation {
|
||||
});
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 0, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 0);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -243,13 +261,17 @@ public class TrackRecordingServiceTestLocation {
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
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);
|
||||
assertEquals(8, trackPoints.size());
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 2),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 3),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 4),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 5),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 6),
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -272,12 +294,26 @@ public class TrackRecordingServiceTestLocation {
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
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);
|
||||
assertTrackPoints(List.of(
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 1),
|
||||
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_AUTOMATIC, 2),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 3),
|
||||
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_START_AUTOMATIC, 4),
|
||||
new Pair<>(TrackPoint.Type.TRACKPOINT, 5),
|
||||
new Pair<>(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
private void assertTrackPoints(List<Pair<TrackPoint.Type, Object>> typeAndAccuracy, List<TrackPoint> actual) {
|
||||
assertEquals(typeAndAccuracy.size(), actual.size());
|
||||
for (int i = 0; i < typeAndAccuracy.size(); i++) {
|
||||
assertEquals(typeAndAccuracy.get(i).first, actual.get(i).getType());
|
||||
if (typeAndAccuracy.get(i).second != null) {
|
||||
assertEquals((Integer) typeAndAccuracy.get(i).second, actual.get(i).getAccuracy(), 0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@@ -203,7 +204,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
|
||||
// Start a track.
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(trackId.isValid());
|
||||
assertNotNull(trackId);
|
||||
assertTrue(service.isRecording());
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
assertNotNull(track);
|
||||
@@ -239,7 +240,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
assertEquals(trackId, track.getId());
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
assertNotNull(trackStatistics);
|
||||
assertTrue(trackStatistics.getStartTime_ms() > 0);
|
||||
assertTrue(trackStatistics.getStopTime_ms() >= trackStatistics.getStartTime_ms());
|
||||
assertTrue(trackStatistics.getStartTime().isAfter(Instant.ofEpochMilli(0)));
|
||||
assertTrue(trackStatistics.getStopTime().isAfter(trackStatistics.getStartTime()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class HandlerServerTest {
|
||||
@Test
|
||||
public void sendTrackPoint() throws InterruptedException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||
int accuracy = 50;
|
||||
|
||||
// when
|
||||
|
||||
@@ -3,10 +3,12 @@ package de.dennisguse.opentracks.services.handlers;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -32,6 +34,12 @@ public class LocationHandlerTest {
|
||||
@InjectMocks
|
||||
private LocationHandler locationHandler;
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Let's use default values.
|
||||
@@ -80,7 +88,6 @@ public class LocationHandlerTest {
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
|
||||
// when
|
||||
// poor latitude
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
|
||||
@@ -32,7 +32,7 @@ public class ElevationSumManagerTest {
|
||||
|
||||
// then
|
||||
Assert.assertEquals(0f, elevationSumManager.getElevationGain_m(), 0.01);
|
||||
Assert.assertEquals(-48.0, elevationSumManager.getElevationLoss_m(), 0.01);
|
||||
Assert.assertEquals(48.0, elevationSumManager.getElevationLoss_m(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -20,7 +20,11 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Tests for {@link TrackStatistics}.
|
||||
@@ -39,16 +43,40 @@ public class TrackStatisticsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMerge() {
|
||||
public void testMerge_no_data() {
|
||||
// given
|
||||
TrackStatistics statistics2 = new TrackStatistics();
|
||||
statistics.setStartTime_ms(1000L); // Resulting start time
|
||||
statistics.setStopTime_ms(2500L);
|
||||
statistics2.setStartTime_ms(3000L);
|
||||
statistics2.setStopTime_ms(4000L); // Resulting stop time
|
||||
statistics.setTotalTime(1500L);
|
||||
statistics2.setTotalTime(1000L); // Result: 1500+1000
|
||||
statistics.setMovingTime(700L);
|
||||
statistics2.setMovingTime(600L); // Result: 700+600
|
||||
|
||||
// when
|
||||
statistics.merge(statistics2);
|
||||
|
||||
// then
|
||||
assertNull(statistics.getStartTime());
|
||||
assertNull(statistics.getStopTime());
|
||||
assertEquals(Duration.ofSeconds(0), statistics.getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(0), statistics.getTotalTime());
|
||||
|
||||
assertNull(statistics.getTotalElevationGain());
|
||||
assertNull(statistics.getTotalElevationLoss());
|
||||
assertEquals(Double.NEGATIVE_INFINITY, statistics.getMaxElevation(), 0.0);
|
||||
assertEquals(Double.POSITIVE_INFINITY, statistics.getMinElevation(), 0.0);
|
||||
assertEquals(0.0, statistics.getMaxSpeed(), 0.0);
|
||||
assertEquals(0.0, statistics.getAverageSpeed(), 0.0);
|
||||
assertEquals(0.0, statistics.getAverageMovingSpeed(), 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMerge() {
|
||||
// given
|
||||
TrackStatistics statistics2 = new TrackStatistics();
|
||||
statistics.setStartTime(Instant.ofEpochMilli(1000)); // Resulting start time
|
||||
statistics.setStopTime(Instant.ofEpochMilli(2500));
|
||||
statistics2.setStartTime(Instant.ofEpochMilli(3000));
|
||||
statistics2.setStopTime(Instant.ofEpochMilli(4000)); // Resulting stop time
|
||||
statistics.setTotalTime(Duration.ofMillis(1500));
|
||||
statistics2.setTotalTime(Duration.ofMillis(1000)); // Result: 1500+1000
|
||||
statistics.setMovingTime(Duration.ofMillis(700));
|
||||
statistics2.setMovingTime(Duration.ofMillis(600)); // Result: 700+600
|
||||
statistics.setTotalDistance(750.0);
|
||||
statistics2.setTotalDistance(350.0); // Result: 750+350
|
||||
statistics.setTotalElevationGain(50.0f);
|
||||
@@ -60,15 +88,17 @@ public class TrackStatisticsTest {
|
||||
statistics2.setMaxElevation(3575.0); // Resulting max elevation
|
||||
statistics2.setMinElevation(2800.0);
|
||||
|
||||
// when
|
||||
statistics.merge(statistics2);
|
||||
|
||||
assertEquals(1000L, statistics.getStartTime_ms());
|
||||
assertEquals(4000L, statistics.getStopTime_ms());
|
||||
assertEquals(2500L, statistics.getTotalTime());
|
||||
assertEquals(1300L, statistics.getMovingTime());
|
||||
// then
|
||||
assertEquals(Instant.ofEpochMilli(1000), statistics.getStartTime());
|
||||
assertEquals(Instant.ofEpochMilli(4000), statistics.getStopTime());
|
||||
assertEquals(Duration.ofMillis(2500), statistics.getTotalTime());
|
||||
assertEquals(Duration.ofMillis(1300), 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(statistics.getTotalDistance() / statistics.getMovingTime().getSeconds(), statistics.getMaxSpeed(), 0.001);
|
||||
assertEquals(1200.0, statistics.getMinElevation(), 0.001);
|
||||
assertEquals(3575.0, statistics.getMaxElevation(), 0.001);
|
||||
}
|
||||
@@ -76,14 +106,14 @@ public class TrackStatisticsTest {
|
||||
@Test
|
||||
public void testGetAverageSpeed() {
|
||||
statistics.setTotalDistance(1000.0);
|
||||
statistics.setTotalTime(50000); // in milliseconds
|
||||
statistics.setTotalTime(Duration.ofMillis(50000));
|
||||
assertEquals(20.0, statistics.getAverageSpeed(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAverageMovingSpeed() {
|
||||
statistics.setTotalDistance(1000.0);
|
||||
statistics.setMovingTime(20000); // in milliseconds
|
||||
statistics.setMovingTime(Duration.ofMillis(20000));
|
||||
assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TrackStatisticsUpdaterTest {
|
||||
|
||||
@Test
|
||||
public void addTrackPoint() {
|
||||
// given
|
||||
TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1));
|
||||
|
||||
// when
|
||||
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
|
||||
data.trackPoints.forEach(it -> updater.addTrackPoint(it, 50));
|
||||
|
||||
// then
|
||||
TrackStatistics statistics = updater.getTrackStatistics();
|
||||
assertEquals(85.35, statistics.getTotalDistance(), 0.01);
|
||||
assertEquals(Duration.ofMillis(13999), statistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(6), statistics.getMovingTime());
|
||||
|
||||
assertEquals(2.5, statistics.getMinElevation(), 0.01);
|
||||
assertEquals(27.5, statistics.getMaxElevation(), 0.01);
|
||||
assertEquals(27, statistics.getTotalElevationGain(), 0.01);
|
||||
assertEquals(27.0, statistics.getTotalElevationLoss(), 0.01);
|
||||
|
||||
assertEquals(14.226, statistics.getMaxSpeed(), 0.01);
|
||||
assertEquals(14.226, statistics.getAverageMovingSpeed(), 0.01);
|
||||
assertEquals(6.566, statistics.getAverageSpeed(), 0.01);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -26,13 +26,15 @@ public class AnnouncementUtilsTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void getAnnouncement() {
|
||||
public void getAnnouncement_metric() {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_metric));
|
||||
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", null);
|
||||
@@ -42,43 +44,26 @@ public class AnnouncementUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval() {
|
||||
public void getAnnouncement_withInterval_metric() {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_metric));
|
||||
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setTotalTime(Duration.ofMillis(600000));
|
||||
stats.setMovingTime(Duration.ofMillis(300000));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
||||
List<TrackPoint> trackPoints = Arrays.asList(TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second);
|
||||
intervalStatistics.build(trackPoints, 1000);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(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 = " Lap speed of " + context.getResources().getQuantityString(speedId, getQuantityCount(kmPerHour), kmPerHour);
|
||||
String msg = firstPartMsg + rateMsg;
|
||||
String expected = "OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour Lap speed of 51.2 kilometers per hour";
|
||||
|
||||
// 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);
|
||||
}
|
||||
assertEquals(expected, announcement);
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class PressureSensorUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(0f, elevationGain_m, 0.01);
|
||||
assertEquals(-15.0f, elevationLoss_m, 0.01);
|
||||
assertEquals(15.0f, elevationLoss_m, 0.01);
|
||||
assertEquals(1016.80f, lastUsedPressureValue_hPa, 0.01); //Expect exponential smoothing
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
@@ -40,55 +41,32 @@ public class StringUtilsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#formatDateTimeIso8601(long)}.
|
||||
*/
|
||||
@Test
|
||||
public void testFormatDateTimeIso8601() {
|
||||
assertEquals("1970-01-01T00:00:12.345Z", StringUtils.formatDateTimeIso8601(12345));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#formatElapsedTime(long)}.
|
||||
* Tests {@link StringUtils#formatElapsedTime(Duration)}.
|
||||
*/
|
||||
@Test
|
||||
public void testformatElapsedTime() {
|
||||
// 1 second
|
||||
assertEquals("00:01", StringUtils.formatElapsedTime(1000));
|
||||
// 10 seconds
|
||||
assertEquals("00:10", StringUtils.formatElapsedTime(10000));
|
||||
// 1 minute
|
||||
assertEquals("01:00", StringUtils.formatElapsedTime(60000));
|
||||
// 10 minutes
|
||||
assertEquals("10:00", StringUtils.formatElapsedTime(600000));
|
||||
// 1 hour
|
||||
assertEquals("1:00:00", StringUtils.formatElapsedTime(3600000));
|
||||
// 10 hours
|
||||
assertEquals("10:00:00", StringUtils.formatElapsedTime(36000000));
|
||||
// 100 hours
|
||||
assertEquals("100:00:00", StringUtils.formatElapsedTime(360000000));
|
||||
assertEquals("00:01", StringUtils.formatElapsedTime(Duration.ofMillis(1000)));
|
||||
assertEquals("00:10", StringUtils.formatElapsedTime(Duration.ofMillis(10000)));
|
||||
assertEquals("01:00", StringUtils.formatElapsedTime(Duration.ofMillis(60000)));
|
||||
assertEquals("10:00", StringUtils.formatElapsedTime(Duration.ofMillis(600000)));
|
||||
assertEquals("1:00:00", StringUtils.formatElapsedTime(Duration.ofMillis(3600000)));
|
||||
assertEquals("10:00:00", StringUtils.formatElapsedTime(Duration.ofMillis(36000000)));
|
||||
assertEquals("100:00:00", StringUtils.formatElapsedTime(Duration.ofMillis(360000000)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#formatElapsedTimeWithHour(long)}.
|
||||
* Tests {@link StringUtils#formatElapsedTimeWithHour(Duration)}.
|
||||
*/
|
||||
@Test
|
||||
public void testformatElapsedTimeWithHour() {
|
||||
// 1 second
|
||||
assertEquals("0:00:01", StringUtils.formatElapsedTimeWithHour(1000));
|
||||
// 10 seconds
|
||||
assertEquals("0:00:10", StringUtils.formatElapsedTimeWithHour(10000));
|
||||
// 1 minute
|
||||
assertEquals("0:01:00", StringUtils.formatElapsedTimeWithHour(60000));
|
||||
// 10 minutes
|
||||
assertEquals("0:10:00", StringUtils.formatElapsedTimeWithHour(600000));
|
||||
// 1 hour
|
||||
assertEquals("1:00:00", StringUtils.formatElapsedTimeWithHour(3600000));
|
||||
// 10 hours
|
||||
assertEquals("10:00:00", StringUtils.formatElapsedTimeWithHour(36000000));
|
||||
// 100 hours
|
||||
assertEquals("100:00:00", StringUtils.formatElapsedTimeWithHour(360000000));
|
||||
assertEquals("0:00:01", StringUtils.formatElapsedTimeWithHour(Duration.ofMillis(1000)));
|
||||
assertEquals("0:00:10", StringUtils.formatElapsedTimeWithHour(Duration.ofMillis(10000)));
|
||||
assertEquals("0:01:00", StringUtils.formatElapsedTimeWithHour(Duration.ofMillis(60000)));
|
||||
assertEquals("0:10:00", StringUtils.formatElapsedTimeWithHour(Duration.ofMillis(600000)));
|
||||
assertEquals("1:00:00", StringUtils.formatElapsedTimeWithHour(Duration.ofMillis(3600000)));
|
||||
assertEquals("10:00:00", StringUtils.formatElapsedTimeWithHour(Duration.ofMillis(36000000)));
|
||||
assertEquals("100:00:00", StringUtils.formatElapsedTimeWithHour(Duration.ofMillis(360000000)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,18 +94,12 @@ public class StringUtilsTest {
|
||||
assertEquals("<![CDATA[hello]]]]><![CDATA[>there]]>", StringUtils.formatCData("hello]]>there"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#parseTime(String)} with fractional seconds.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_fractional() {
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
assertGetTime("2010-05-04T03:02:01.3529Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#parseTime(String)} with time zone.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_timezone() {
|
||||
assertGetTime("2010-05-04T03:02:01", 2010, 5, 4, 3, 2, 1, 0);
|
||||
@@ -140,9 +112,6 @@ public class StringUtilsTest {
|
||||
assertGetTime("2010-05-04T03:02:01-05:00", 2010, 5, 4, 8, 2, 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#parseTime(String)} with fractional seconds and time zone.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTime_fractionalAndTimezone() {
|
||||
assertGetTime("2010-05-04T03:02:01.352Z", 2010, 5, 4, 3, 2, 1, 352);
|
||||
@@ -170,29 +139,7 @@ public class StringUtilsTest {
|
||||
|
||||
// This comparision tends to be flaky (difference of 1ms)
|
||||
// Assert.assertEquals(calendar.getTimeInMillis(), StringUtils.parseTime(xmlDateTime));
|
||||
assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime)) <= 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#getTimeParts(long)} with a positive number.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTimeParts_positive() {
|
||||
int[] parts = StringUtils.getTimeParts(61000);
|
||||
assertEquals(1, parts[0]);
|
||||
assertEquals(1, parts[1]);
|
||||
assertEquals(0, parts[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link StringUtils#getTimeParts(long)} with a negative number.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTimeParts_negative() {
|
||||
int[] parts = StringUtils.getTimeParts(-61000);
|
||||
assertEquals(-1, parts[0]);
|
||||
assertEquals(-1, parts[1]);
|
||||
assertEquals(0, parts[2]);
|
||||
assertTrue(calendar.getTimeInMillis() + " vs. " + StringUtils.parseTime(xmlDateTime), Math.abs(calendar.getTimeInMillis() - StringUtils.parseTime(xmlDateTime).toEpochMilli()) <= 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.time.Instant;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -42,7 +42,7 @@ import static org.junit.Assert.assertEquals;
|
||||
public class TrackNameUtilsTest {
|
||||
|
||||
private static final Track.Id TRACK_ID = new Track.Id(1L);
|
||||
private static final long START_TIME = 1288213406000L;
|
||||
private static final Instant START_TIME = Instant.ofEpochMilli(1288213406000L);
|
||||
|
||||
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@@ -62,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);
|
||||
assertEquals(simpleDateFormat.format(new Date(START_TIME)), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TrackPointUtilsTest {
|
||||
|
||||
@Test
|
||||
public void fixTime_none() {
|
||||
// given
|
||||
long time = System.currentTimeMillis();
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
trackPoint.setTime(time);
|
||||
|
||||
// when
|
||||
TrackPointUtils.fixTime(trackPoint);
|
||||
|
||||
// then
|
||||
assertEquals(time, trackPoint.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fixTime_0() {
|
||||
// given
|
||||
long time = System.currentTimeMillis();
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
trackPoint.setTime(0);
|
||||
|
||||
// when
|
||||
TrackPointUtils.fixTime(trackPoint);
|
||||
|
||||
// then
|
||||
assertEquals(time, trackPoint.getTime(), 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fixTime_gpsWeekRollover() {
|
||||
// given
|
||||
long time = System.currentTimeMillis();
|
||||
TrackPoint trackPoint = new TrackPoint();
|
||||
trackPoint.setTime(time - 1024 * UnitConversions.ONE_WEEK_MS);
|
||||
|
||||
// when
|
||||
TrackPointUtils.fixTime(trackPoint);
|
||||
|
||||
// then
|
||||
assertEquals(time, trackPoint.getTime());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,10 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -21,19 +25,12 @@ public class AggregatedStatisticsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
/**
|
||||
* Create a TrackStatistics object.
|
||||
*
|
||||
* @param totalDistance distance in meters.
|
||||
* @param totalTime total time in milliseconds.
|
||||
* @return TrackStatistics object.
|
||||
*/
|
||||
private static Track createTrack(Context context, long totalDistance, long totalTime, String category) {
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setStartTime_ms(1000L); // Resulting start time
|
||||
statistics.setStopTime_ms(1000L + totalTime);
|
||||
statistics.setTotalTime(totalTime);
|
||||
statistics.setMovingTime(totalTime);
|
||||
statistics.setStartTime(Instant.ofEpochMilli(1000L)); // Resulting start time
|
||||
statistics.setStopTime(Instant.ofEpochMilli(1000L + totalTime));
|
||||
statistics.setTotalTime(Duration.ofMillis(totalTime));
|
||||
statistics.setMovingTime(Duration.ofMillis(totalTime));
|
||||
statistics.setTotalDistance(totalDistance);
|
||||
statistics.setTotalElevationGain(50.0f);
|
||||
statistics.setMaxSpeed(50.0); // Resulting max speed
|
||||
@@ -57,8 +54,7 @@ public class AggregatedStatisticsTest {
|
||||
Track track = createTrack(context, totalDistance, totalTime, biking);
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(track);
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
|
||||
|
||||
// then
|
||||
assertEquals(1, aggregatedStatistics.getCount());
|
||||
@@ -67,7 +63,7 @@ public class AggregatedStatisticsTest {
|
||||
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,8 +76,7 @@ public class AggregatedStatisticsTest {
|
||||
Track track = createTrack(context, totalDistance, totalTime, mountainBiking);
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(track);
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
|
||||
|
||||
// then
|
||||
assertNotNull(aggregatedStatistics.get(mountainBiking));
|
||||
@@ -97,8 +92,7 @@ public class AggregatedStatisticsTest {
|
||||
Track track = createTrack(context, totalDistance, totalTime, trailRunning);
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(track);
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
|
||||
|
||||
// then
|
||||
assertNotNull(aggregatedStatistics.get(trailRunning));
|
||||
@@ -111,15 +105,10 @@ public class AggregatedStatisticsTest {
|
||||
long totalDistance = 10000;
|
||||
long totalTime = 2400000;
|
||||
String biking = context.getString(R.string.activity_type_biking);
|
||||
Track[] tracks = new Track[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
tracks[i] = createTrack(context, totalDistance, totalTime, biking);
|
||||
}
|
||||
List<Track> tracks = List.of(createTrack(context, totalDistance, totalTime, biking), createTrack(context, totalDistance, totalTime, biking));
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
|
||||
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
||||
|
||||
// then
|
||||
assertEquals(1, aggregatedStatistics.getCount());
|
||||
@@ -128,7 +117,7 @@ public class AggregatedStatisticsTest {
|
||||
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(totalTime * 2L, statistics2.getMovingTime());
|
||||
assertEquals(Duration.ofMillis(totalTime * 2), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -141,17 +130,14 @@ public class AggregatedStatisticsTest {
|
||||
String running = context.getString(R.string.activity_type_running);
|
||||
String walking = context.getString(R.string.activity_type_walking);
|
||||
long totalTime = 2400000;
|
||||
Track[] tracks = new Track[]{
|
||||
List<Track> tracks = List.of(
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, running),
|
||||
createTrack(context, totalDistance, totalTime, walking)
|
||||
};
|
||||
);
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
|
||||
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
|
||||
aggregatedStatistics.aggregate(tracks[2]); // biking activity 3.
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
||||
|
||||
// then
|
||||
assertEquals(3, aggregatedStatistics.getCount());
|
||||
@@ -165,19 +151,19 @@ public class AggregatedStatisticsTest {
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +177,7 @@ public class AggregatedStatisticsTest {
|
||||
String running = context.getString(R.string.activity_type_running);
|
||||
String walking = context.getString(R.string.activity_type_walking);
|
||||
String driving = context.getString(R.string.activity_type_driving);
|
||||
Track[] tracks = new Track[]{
|
||||
List<Track> tracks = List.of(
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, running),
|
||||
createTrack(context, totalDistance, totalTime, walking),
|
||||
@@ -201,16 +187,12 @@ public class AggregatedStatisticsTest {
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, biking),
|
||||
createTrack(context, totalDistance, totalTime, driving),
|
||||
};
|
||||
createTrack(context, totalDistance, totalTime, driving)
|
||||
);
|
||||
|
||||
|
||||
// when
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
|
||||
for (Track track : tracks) {
|
||||
aggregatedStatistics.aggregate(track);
|
||||
|
||||
}
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
||||
|
||||
// then
|
||||
// 4 sports.
|
||||
@@ -232,28 +214,35 @@ public class AggregatedStatisticsTest {
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
|
||||
assertEquals(totalDistance * 5, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(totalTime * 5, statistics2.getMovingTime());
|
||||
assertEquals(Duration.ofMillis(totalTime * 5), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Running.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
|
||||
assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(totalTime * 2, statistics2.getMovingTime());
|
||||
assertEquals(Duration.ofMillis(totalTime * 2), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Walking.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
|
||||
assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(totalTime * 2, statistics2.getMovingTime());
|
||||
assertEquals(Duration.ofMillis(totalTime * 2), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Driving.
|
||||
{
|
||||
TrackStatistics statistics2 = aggregatedStatistics.get(driving).getTrackStatistics();
|
||||
assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
|
||||
assertEquals(totalTime, statistics2.getMovingTime());
|
||||
assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime());
|
||||
}
|
||||
|
||||
// Check order
|
||||
|
||||
{
|
||||
assertEquals(biking, aggregatedStatistics.getItem(0).getCategory());
|
||||
assertEquals(driving, aggregatedStatistics.getItem(3).getCategory());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
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;
|
||||
@@ -24,17 +21,16 @@ 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);
|
||||
return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).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();
|
||||
}
|
||||
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
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.
|
||||
@@ -47,10 +43,9 @@ public class IntervalStatisticsTest {
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(50);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 1000f;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,10 +59,9 @@ public class IntervalStatisticsTest {
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 1000f;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,10 +75,9 @@ public class IntervalStatisticsTest {
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(200);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 3000f;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,10 +91,9 @@ public class IntervalStatisticsTest {
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(1000);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 3000f;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,27 +107,26 @@ public class IntervalStatisticsTest {
|
||||
List<TrackPoint> trackPoints = buildTrackPoints(10000);
|
||||
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
|
||||
float distanceInterval = 1000f;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
||||
|
||||
// when and then
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval, intervalStatistics);
|
||||
whenAndThen(trackPoints, trackStatistics, distanceInterval);
|
||||
}
|
||||
|
||||
private void whenAndThen(List<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval, IntervalStatistics intervalStatistics) {
|
||||
intervalStatistics.build(trackPoints, distanceInterval);
|
||||
private void whenAndThen(List<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval) {
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, distanceInterval);
|
||||
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
|
||||
double totalDistance = 0d;
|
||||
long totalTime = 0L;
|
||||
float totalTime = 0L;
|
||||
float totalGain = 0f;
|
||||
for (IntervalStatistics.Interval i : intervalList) {
|
||||
totalDistance += i.getDistance_m();
|
||||
totalTime += ((i.getDistance_m() / i.getSpeed_ms()) * UnitConversions.S_TO_MS);
|
||||
totalTime += i.getDistance_m() / i.getSpeed_ms();
|
||||
totalGain += i.getGain_m();
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals(trackStatistics.getTotalDistance(), totalDistance, 0.01);
|
||||
assertEquals(trackStatistics.getTotalTime() * UnitConversions.MS_TO_S, totalTime * UnitConversions.MS_TO_S, 0.1);
|
||||
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1);
|
||||
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance() / distanceInterval));
|
||||
assertEquals(totalGain, trackPoints.size() * TestDataUtil.ELEVATION_GAIN, 0.1);
|
||||
for (int i = 0; i < intervalList.size() - 1; i++) {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This hidden file is here to ensure there is a res folder.
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:gpxtpx="http://www.garmin.com/xmlschemes/TrackPointExtension/v2" xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1" xmlns:topografix="http://www.topografix.com/GPX/Private/TopoGrafix/0/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1" creator="OpenTracks (Debug)" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
|
||||
<metadata>
|
||||
<name><![CDATA[2021-01-07 22:51]]></name>
|
||||
<desc><![CDATA[the description]]></desc>
|
||||
</metadata>
|
||||
<wpt lat="3" lon="14">
|
||||
<ele>10</ele>
|
||||
<time>2021-01-07T21:51:59.179Z</time>
|
||||
<name><![CDATA[Marker 1]]></name>
|
||||
<desc><![CDATA[Marker 1 desc]]></desc>
|
||||
<type><![CDATA[Marker 1 category]]></type>
|
||||
</wpt>
|
||||
<wpt lat="3" lon="14.002">
|
||||
<ele>10</ele>
|
||||
<time>2021-01-07T21:52:01.010Z</time>
|
||||
<name><![CDATA[Marker 2]]></name>
|
||||
<desc><![CDATA[Marker 2 desc]]></desc>
|
||||
<type><![CDATA[Marker 2 category]]></type>
|
||||
</wpt>
|
||||
<trk>
|
||||
<name><![CDATA[2021-01-07 22:51]]></name>
|
||||
<desc><![CDATA[the description]]></desc>
|
||||
<type><![CDATA[the category]]></type>
|
||||
<extensions>
|
||||
<topografix:color>c0c0c0</topografix:color>
|
||||
<opentracks:trackid>7002101e-4198-4613-8c24-544e01ca3981</opentracks:trackid>
|
||||
</extensions>
|
||||
<trkseg>
|
||||
<trkpt lat="3" lon="14">
|
||||
<ele>10</ele>
|
||||
<time>2021-01-07T21:51:59.179Z</time>
|
||||
</trkpt>
|
||||
<trkpt lat="3" lon="14.001">
|
||||
<ele>10</ele>
|
||||
<time>2021-01-07T21:52:00.653Z</time>
|
||||
</trkpt>
|
||||
<trkpt lat="3" lon="14.002">
|
||||
<ele>10</ele>
|
||||
<time>2021-01-07T21:52:01.010Z</time>
|
||||
</trkpt>
|
||||
<trkpt lat="100" lon="0">
|
||||
<time>2021-01-07T21:52:02.658Z</time>
|
||||
</trkpt>
|
||||
|
||||
<trkpt lat="200" lon="0">
|
||||
<time>2021-01-07T21:52:03.873Z</time>
|
||||
</trkpt>
|
||||
<trkpt lat="3" lon="14.003">
|
||||
<ele>10</ele>
|
||||
<time>2021-01-07T21:52:04.103Z</time>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kml xmlns="http://www.opengis.net/kml/2.2"
|
||||
xmlns:gx="http://www.google.com/kml/ext/2.2"
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1">
|
||||
<Document>
|
||||
<open>1</open>
|
||||
<visibility>1</visibility>
|
||||
<name><![CDATA[2020-08-24 20:57]]></name>
|
||||
<atom:generator><![CDATA[OpenTracks]]></atom:generator>
|
||||
<Style id="track">
|
||||
<LineStyle><color>7f0000ff</color><width>4</width></LineStyle>
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon><href>http://earth.google.com/images/kml-icons/track-directional/track-0.png</href></Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
<Style id="start"><IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-circle.png</href></Icon>
|
||||
<hotSpot x="32" y="1" xunits="pixels" yunits="pixels"/>
|
||||
</IconStyle></Style>
|
||||
<Style id="end"><IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon><href>http://maps.google.com/mapfiles/kml/paddle/red-circle.png</href></Icon>
|
||||
<hotSpot x="32" y="1" xunits="pixels" yunits="pixels"/>
|
||||
</IconStyle></Style>
|
||||
<Style id="waypoint"><IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon><href>http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png</href></Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels"/>
|
||||
</IconStyle></Style>
|
||||
<Schema id="schema">
|
||||
<gx:SimpleArrayField name="speed" type="float">
|
||||
<displayName><![CDATA[Speed (m/s)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="power" type="float">
|
||||
<displayName><![CDATA[Power (W)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="cadence" type="float">
|
||||
<displayName><![CDATA[Cadence (rpm)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="heart_rate" type="float">
|
||||
<displayName><![CDATA[Heart rate (bpm)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
</Schema>
|
||||
<Placemark>
|
||||
<name><![CDATA[2020-08-24 20:57]]></name>
|
||||
<description><![CDATA[]]></description>
|
||||
<icon><![CDATA[UNKNOWN]]></icon>
|
||||
<opentracks:trackid>9316e527-a5b8-47c5-bdbd-954b4be7d80e</opentracks:trackid>
|
||||
<styleUrl>#track</styleUrl>
|
||||
<ExtendedData>
|
||||
<Data name="type"><value><![CDATA[unknown]]></value></Data>
|
||||
</ExtendedData>
|
||||
<gx:MultiTrack>
|
||||
<altitudeMode>absolute</altitudeMode>
|
||||
<gx:interpolate>1</gx:interpolate>
|
||||
</gx:MultiTrack>
|
||||
</Placemark>
|
||||
</Document>
|
||||
</kml>
|
||||
@@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1">
|
||||
<Document>
|
||||
<open>1</open>
|
||||
<visibility>1</visibility>
|
||||
<name><![CDATA[2020-11-28 18:06]]></name>
|
||||
<atom:generator><![CDATA[OpenTracks (Debug)]]></atom:generator>
|
||||
<Style id="track">
|
||||
<LineStyle>
|
||||
<color>7f0000ff</color>
|
||||
<width>4</width>
|
||||
</LineStyle>
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://earth.google.com/images/kml-icons/track-directional/track-0.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
<Style id="start">
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/paddle/grn-circle.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="32" y="1" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
</Style>
|
||||
<Style id="end">
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/paddle/red-circle.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="32" y="1" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
</Style>
|
||||
<Style id="waypoint">
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
</Style>
|
||||
<Schema id="schema">
|
||||
<gx:SimpleArrayField name="speed" type="float">
|
||||
<displayName><![CDATA[Speed (m/s)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="power" type="float">
|
||||
<displayName><![CDATA[Power (W)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="cadence" type="float">
|
||||
<displayName><![CDATA[Cadence (rpm)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="heart_rate" type="float">
|
||||
<displayName><![CDATA[Heart rate (bpm)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
</Schema>
|
||||
<Folder>
|
||||
<name><![CDATA[Sun, 28 Nov 2020 18:06 Markers]]></name>
|
||||
<open>1</open>
|
||||
<Placemark>
|
||||
<name><![CDATA[Split 0]]></name>
|
||||
<description><![CDATA[Total distance: 0.00 km (0.0 mi)
|
||||
Total time: 00:00
|
||||
Moving time: 00:00
|
||||
Average speed: 0.00 km/h (0.0 mi/h)
|
||||
Average moving speed: 0.00 km/h (0.0 mi/h)
|
||||
Max speed: 0.00 km/h (0.0 mi/h)
|
||||
Average pace: 0:00 min/km (0:00 min/mi)
|
||||
Average moving pace: 0:00 min/km (0:00 min/mi)
|
||||
Fastest pace: 0:00 min/km (0:00 min/mi)
|
||||
Max elevation: -9223372036854775808 m (-9223372036854775808 ft)
|
||||
Min elevation: 9223372036854775807 m (9223372036854775807 ft)
|
||||
Elevation gain: 0 m (0 ft)
|
||||
Max grade: 0 %
|
||||
Min grade: 0 %
|
||||
Recorded: 6/23 11:29]]></description>
|
||||
<TimeStamp>
|
||||
<when>1970-01-01T00:00:00Z</when>
|
||||
</TimeStamp>
|
||||
<styleUrl>#waypoint</styleUrl>
|
||||
<Point>
|
||||
<coordinates>180.0,100.0</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Placemark>
|
||||
<name><![CDATA[2020-11-28 18:06 (Start)]]></name>
|
||||
<description />
|
||||
<TimeStamp>
|
||||
<when>2020-11-28T17:06:22.401Z</when>
|
||||
</TimeStamp>
|
||||
<styleUrl>#start</styleUrl>
|
||||
<Point>
|
||||
<coordinates>1.234156,12.340097,469.286376953125</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name><![CDATA[2020-11-28 18:06]]></name>
|
||||
<description><![CDATA[Test Track]]></description>
|
||||
<icon><![CDATA[UNKNOWN]]></icon>
|
||||
<opentracks:trackid>2f170875-55d7-445d-b2b8-22a573c81dbc</opentracks:trackid>
|
||||
<styleUrl>#track</styleUrl>
|
||||
<ExtendedData>
|
||||
<Data name="type">
|
||||
<value><![CDATA[unknown]]></value>
|
||||
</Data>
|
||||
</ExtendedData>
|
||||
<gx:MultiTrack>
|
||||
<altitudeMode>absolute</altitudeMode>
|
||||
<gx:interpolate>1</gx:interpolate>
|
||||
<gx:Track>
|
||||
<when>2020-11-28T17:06:22.401Z</when>
|
||||
<gx:coord>1.234156 12.340097 469.286376953125</gx:coord>
|
||||
<when>2020-11-28T17:06:25.448Z</when>
|
||||
<gx:coord>1.23415 12.340036 439.1626281738281</gx:coord>
|
||||
<when>2020-11-28T17:06:47.888Z</when>
|
||||
<gx:coord>1.23405 12.340057 421.8070983886719</gx:coord>
|
||||
<ExtendedData>
|
||||
<SchemaData schemaUrl="#schema">
|
||||
<gx:SimpleArrayData name="speed">
|
||||
<gx:value>0.5391469</gx:value>
|
||||
<gx:value>0.15777911</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
</gx:SimpleArrayData>
|
||||
<gx:SimpleArrayData name="elevation_gain">
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
</gx:SimpleArrayData>
|
||||
</SchemaData>
|
||||
</ExtendedData>
|
||||
</gx:Track>
|
||||
<gx:Track>
|
||||
<when>2020-11-28T17:06:55.861Z</when>
|
||||
<gx:coord>1.23405 12.340057 419.93902587890625</gx:coord>
|
||||
<when>2020-11-28T17:06:56.905Z</when>
|
||||
<gx:coord>1.23405 12.340057 419.9036560058594</gx:coord>
|
||||
<when>2020-11-28T17:07:20.870Z</when>
|
||||
<gx:coord>1.234046 12.340082 417.99432373046875</gx:coord>
|
||||
<ExtendedData>
|
||||
<SchemaData schemaUrl="#schema">
|
||||
<gx:SimpleArrayData name="speed">
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
</gx:SimpleArrayData>
|
||||
<gx:SimpleArrayData name="elevation_gain">
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
</gx:SimpleArrayData>
|
||||
</SchemaData>
|
||||
</ExtendedData>
|
||||
</gx:Track>
|
||||
</gx:MultiTrack>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name><![CDATA[2020-11-28 18:06 (End)]]></name>
|
||||
<description><![CDATA[OpenTracks (Debug)
|
||||
|
||||
Name: 2020-11-28 18:06
|
||||
Activity type: unknown
|
||||
Description: -
|
||||
Total distance: 0.01 km (0.0 mi)
|
||||
Total time: 01:04
|
||||
Moving time: 00:00
|
||||
Average speed: 0.49 km/h (0.3 mi/h)
|
||||
Average moving speed: 0.00 km/h (0.0 mi/h)
|
||||
Max speed: 0.00 km/h (0.0 mi/h)
|
||||
Average pace: 122:30 min/km (197:08 min/mi)
|
||||
Average moving pace: 0:00 min/km (0:00 min/mi)
|
||||
Fastest pace: 0:00 min/km (0:00 min/mi)
|
||||
Max elevation: 469 m (1540 ft)
|
||||
Min elevation: 419 m (1376 ft)
|
||||
Elevation gain: 0 m (0 ft)
|
||||
Recorded: 11/28 18:06]]></description>
|
||||
<TimeStamp>
|
||||
<when>2020-11-28T17:07:20.870Z</when>
|
||||
</TimeStamp>
|
||||
<styleUrl>#end</styleUrl>
|
||||
<Point>
|
||||
<coordinates>1.234046,12.340082,417.99432373046875</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
</Document>
|
||||
</kml>
|
||||
@@ -1,173 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kml xmlns="http://www.opengis.net/kml/2.2"
|
||||
xmlns:gx="http://www.google.com/kml/ext/2.2"
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1">
|
||||
<Document>
|
||||
<open>1</open>
|
||||
<visibility>1</visibility>
|
||||
<name><![CDATA[2020-11-28 18:06]]></name>
|
||||
<atom:generator><![CDATA[OpenTracks (Debug)]]></atom:generator>
|
||||
<Style id="track">
|
||||
<LineStyle><color>7f0000ff</color><width>4</width></LineStyle>
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon><href>http://earth.google.com/images/kml-icons/track-directional/track-0.png</href></Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
<Style id="start"><IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon><href>http://maps.google.com/mapfiles/kml/paddle/grn-circle.png</href></Icon>
|
||||
<hotSpot x="32" y="1" xunits="pixels" yunits="pixels"/>
|
||||
</IconStyle></Style>
|
||||
<Style id="end"><IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon><href>http://maps.google.com/mapfiles/kml/paddle/red-circle.png</href></Icon>
|
||||
<hotSpot x="32" y="1" xunits="pixels" yunits="pixels"/>
|
||||
</IconStyle></Style>
|
||||
<Style id="waypoint"><IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon><href>http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png</href></Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels"/>
|
||||
</IconStyle></Style>
|
||||
<Schema id="schema">
|
||||
<gx:SimpleArrayField name="speed" type="float">
|
||||
<displayName><![CDATA[Speed (m/s)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="power" type="float">
|
||||
<displayName><![CDATA[Power (W)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="cadence" type="float">
|
||||
<displayName><![CDATA[Cadence (rpm)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
<gx:SimpleArrayField name="heart_rate" type="float">
|
||||
<displayName><![CDATA[Heart rate (bpm)]]></displayName>
|
||||
</gx:SimpleArrayField>
|
||||
</Schema>
|
||||
<Folder>
|
||||
<name><![CDATA[Sun, 28 Nov 2020 18:06 Markers]]></name>
|
||||
<open>1</open>
|
||||
<Placemark>
|
||||
<name><![CDATA[Split 0]]></name>
|
||||
<description><![CDATA[Total distance: 0.00 km (0.0 mi)
|
||||
Total time: 00:00
|
||||
Moving time: 00:00
|
||||
Average speed: 0.00 km/h (0.0 mi/h)
|
||||
Average moving speed: 0.00 km/h (0.0 mi/h)
|
||||
Max speed: 0.00 km/h (0.0 mi/h)
|
||||
Average pace: 0:00 min/km (0:00 min/mi)
|
||||
Average moving pace: 0:00 min/km (0:00 min/mi)
|
||||
Fastest pace: 0:00 min/km (0:00 min/mi)
|
||||
Max elevation: -9223372036854775808 m (-9223372036854775808 ft)
|
||||
Min elevation: 9223372036854775807 m (9223372036854775807 ft)
|
||||
Elevation gain: 0 m (0 ft)
|
||||
Max grade: 0 %
|
||||
Min grade: 0 %
|
||||
Recorded: 6/23 11:29
|
||||
]]></description>
|
||||
<TimeStamp><when>1970-01-01T00:00:00Z</when></TimeStamp>
|
||||
<styleUrl>#waypoint</styleUrl>
|
||||
<Point>
|
||||
<coordinates>180.0,100.0</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Placemark>
|
||||
<name><![CDATA[2020-11-28 18:06 (Start)]]></name>
|
||||
<description><![CDATA[]]></description>
|
||||
<TimeStamp><when>2020-11-28T17:06:22.401Z</when></TimeStamp>
|
||||
<styleUrl>#start</styleUrl>
|
||||
<Point>
|
||||
<coordinates>1.234156,12.340097,469.286376953125</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name><![CDATA[2020-11-28 18:06]]></name>
|
||||
<description><![CDATA[Test Track]]></description>
|
||||
<icon><![CDATA[UNKNOWN]]></icon>
|
||||
<opentracks:trackid>2f170875-55d7-445d-b2b8-22a573c81dbc</opentracks:trackid>
|
||||
<styleUrl>#track</styleUrl>
|
||||
<ExtendedData>
|
||||
<Data name="type"><value><![CDATA[unknown]]></value></Data>
|
||||
</ExtendedData>
|
||||
<gx:MultiTrack>
|
||||
<altitudeMode>absolute</altitudeMode>
|
||||
<gx:interpolate>1</gx:interpolate>
|
||||
<gx:Track>
|
||||
<when>2020-11-28T17:06:22.401Z</when>
|
||||
<gx:coord>1.234156 12.340097 469.286376953125</gx:coord>
|
||||
<when>2020-11-28T17:06:25.448Z</when>
|
||||
<gx:coord>1.23415 12.340036 439.1626281738281</gx:coord>
|
||||
<when>2020-11-28T17:06:47.888Z</when>
|
||||
<gx:coord>1.23405 12.340057 421.8070983886719</gx:coord>
|
||||
<ExtendedData>
|
||||
<SchemaData schemaUrl="#schema">
|
||||
<gx:SimpleArrayData name="speed">
|
||||
<gx:value>0.5391469</gx:value>
|
||||
<gx:value>0.15777911</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
</gx:SimpleArrayData>
|
||||
<gx:SimpleArrayData name="elevation_gain">
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
</gx:SimpleArrayData>
|
||||
</SchemaData>
|
||||
</ExtendedData>
|
||||
</gx:Track>
|
||||
<gx:Track>
|
||||
<when>2020-11-28T17:06:55.861Z</when>
|
||||
<gx:coord>1.23405 12.340057 419.93902587890625</gx:coord>
|
||||
<when>2020-11-28T17:06:56.905Z</when>
|
||||
<gx:coord>1.23405 12.340057 419.9036560058594</gx:coord>
|
||||
<when>2020-11-28T17:07:20.870Z</when>
|
||||
<gx:coord>1.234046 12.340082 417.99432373046875</gx:coord>
|
||||
<ExtendedData>
|
||||
<SchemaData schemaUrl="#schema">
|
||||
<gx:SimpleArrayData name="speed">
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
</gx:SimpleArrayData>
|
||||
<gx:SimpleArrayData name="elevation_gain">
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
<gx:value>0.0</gx:value>
|
||||
</gx:SimpleArrayData>
|
||||
</SchemaData>
|
||||
</ExtendedData>
|
||||
</gx:Track>
|
||||
</gx:MultiTrack>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name><![CDATA[2020-11-28 18:06 (End)]]></name>
|
||||
<description><![CDATA[OpenTracks (Debug)
|
||||
|
||||
Name: 2020-11-28 18:06
|
||||
Activity type: unknown
|
||||
Description: -
|
||||
Total distance: 0.01 km (0.0 mi)
|
||||
Total time: 01:04
|
||||
Moving time: 00:00
|
||||
Average speed: 0.49 km/h (0.3 mi/h)
|
||||
Average moving speed: 0.00 km/h (0.0 mi/h)
|
||||
Max speed: 0.00 km/h (0.0 mi/h)
|
||||
Average pace: 122:30 min/km (197:08 min/mi)
|
||||
Average moving pace: 0:00 min/km (0:00 min/mi)
|
||||
Fastest pace: 0:00 min/km (0:00 min/mi)
|
||||
Max elevation: 469 m (1540 ft)
|
||||
Min elevation: 419 m (1376 ft)
|
||||
Elevation gain: 0 m (0 ft)
|
||||
Recorded: 11/28 18:06
|
||||
]]></description>
|
||||
<TimeStamp><when>2020-11-28T17:07:20.870Z</when></TimeStamp>
|
||||
<styleUrl>#end</styleUrl>
|
||||
<Point>
|
||||
<coordinates>1.234046,12.340082,417.99432373046875</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
</Document>
|
||||
</kml>
|
||||
@@ -14,5 +14,5 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">OpenTracks (Debug)</string>
|
||||
<string name="app_name" translatable="false">OpenTracks (Debug)</string>
|
||||
</resources>
|
||||
|
||||
@@ -19,6 +19,7 @@ package de.dennisguse.opentracks;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
@@ -39,8 +40,22 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
||||
setContentView(getRootView());
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
setupActionBarBack(toolbar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract View getRootView();
|
||||
|
||||
protected void setupActionBarBack(@Nullable Toolbar toolbar) {
|
||||
setSupportActionBar(toolbar);
|
||||
if (toolbar != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,15 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
|
||||
Object bundleMarkerId = getIntent().getExtras().get(EXTRA_MARKER_ID);
|
||||
Marker.Id markerId = null;
|
||||
if (bundleMarkerId instanceof Marker.Id) {
|
||||
markerId = (Marker.Id) bundleMarkerId;
|
||||
}
|
||||
if (bundleMarkerId instanceof Long) {
|
||||
//Incoming Intent via Dashboard API.
|
||||
markerId = new Marker.Id((Long) bundleMarkerId);
|
||||
}
|
||||
if (markerId == null) {
|
||||
Log.d(TAG, "invalid marker id");
|
||||
finish();
|
||||
|
||||
@@ -33,20 +33,16 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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.databinding.MarkerEditBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.MarkerEditViewModel;
|
||||
|
||||
/**
|
||||
* An activity to add/edit a marker.
|
||||
@@ -58,25 +54,20 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
private static final String BUNDLE_PHOTO_URI = "photo_uri";
|
||||
|
||||
private static final int CAMERA_REQUEST_CODE = 5;
|
||||
private static final int GALLERY_IMG_REQUEST_CODE = 7;
|
||||
|
||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||
private Track.Id trackId;
|
||||
private final TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||
private Marker marker;
|
||||
|
||||
private MenuItem insertPhotoMenuItem;
|
||||
private MenuItem insertGalleryImgMenuItem;
|
||||
|
||||
private Uri photoUri;
|
||||
private Uri photoUriOriginal;
|
||||
private final List<Uri> photoUriDeleteList = new ArrayList<>();
|
||||
private boolean hasCamera;
|
||||
private Uri cameraPhotoUri;
|
||||
|
||||
private boolean isNewMarker;
|
||||
private MarkerEditViewModel viewModel;
|
||||
|
||||
// UI elements
|
||||
private MarkerEditBinding viewBinding;
|
||||
@@ -93,118 +84,39 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
// Setup UI elements
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.marker_types, android.R.layout.simple_dropdown_item_1line);
|
||||
viewBinding.markerEditMarkerType.setAdapter(adapter);
|
||||
viewBinding.markerEditPhotoDelete.setOnClickListener(v -> {
|
||||
if (marker != null && marker.hasPhoto()) {
|
||||
marker.setPhotoUrl(null);
|
||||
}
|
||||
viewBinding.markerEditPhoto.setImageBitmap(null);
|
||||
photoUriDeleteList.add(photoUri);
|
||||
photoUriOriginal = photoUriOriginal == null ? photoUri : photoUriOriginal;
|
||||
photoUri = null;
|
||||
hideAndShowOptions();
|
||||
});
|
||||
viewBinding.markerEditPhotoDelete.setOnClickListener(v -> viewModel.onPhotoDelete(viewBinding.markerEditName.getText().toString(),
|
||||
viewBinding.markerEditMarkerType.getText().toString(),
|
||||
viewBinding.markerEditDescription.getText().toString()));
|
||||
|
||||
viewBinding.markerEditCancel.setOnClickListener(v -> {
|
||||
Track.Id trackId = getTrackId();
|
||||
if (isNewMarker && trackId != null) {
|
||||
// new marker and user cancel -> delete all photos from track directory
|
||||
for (Uri photoUriDelete : photoUriDeleteList) {
|
||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUriDelete);
|
||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
||||
}
|
||||
if (photoUri != null) {
|
||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUri);
|
||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
||||
}
|
||||
} else if (!isNewMarker && trackId != null) {
|
||||
// no new marker, user cancel and photo was changed -> delete all photos but original one
|
||||
for (Uri photoUri : photoUriDeleteList) {
|
||||
if (photoUriOriginal != photoUri) {
|
||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUri);
|
||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
||||
}
|
||||
}
|
||||
if (photoUri != null && photoUri != photoUriOriginal) {
|
||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUri);
|
||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
||||
}
|
||||
}
|
||||
viewModel.onCancel();
|
||||
finish();
|
||||
});
|
||||
|
||||
isNewMarker = markerId == null;
|
||||
|
||||
boolean isNewMarker = markerId == null;
|
||||
setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit);
|
||||
viewBinding.markerEditDone.setText(isNewMarker ? R.string.generic_add : R.string.generic_save);
|
||||
viewBinding.markerEditDone.setOnClickListener(v -> {
|
||||
if (isNewMarker) {
|
||||
addMarker();
|
||||
} else {
|
||||
saveMarker();
|
||||
}
|
||||
Track.Id trackId = getTrackId();
|
||||
if (trackId == null) {
|
||||
for (Uri photoUri : photoUriDeleteList) {
|
||||
File photoFile = FileUtils.getPhotoFileIfExists(this, trackId, photoUri);
|
||||
FileUtils.deleteDirectoryRecurse(photoFile);
|
||||
}
|
||||
}
|
||||
viewModel.onDone(viewBinding.markerEditName.getText().toString(),
|
||||
viewBinding.markerEditMarkerType.getText().toString(),
|
||||
viewBinding.markerEditDescription.getText().toString());
|
||||
finish();
|
||||
});
|
||||
|
||||
if (isNewMarker) {
|
||||
int nextMarkerNumber = trackId == null ? -1 : new ContentProviderUtils(this).getNextMarkerNumber(trackId);
|
||||
if (nextMarkerNumber == -1) {
|
||||
nextMarkerNumber = 0;
|
||||
}
|
||||
viewBinding.markerEditName.setText(getString(R.string.marker_name_format, nextMarkerNumber));
|
||||
viewBinding.markerEditName.selectAll();
|
||||
viewBinding.markerEditMarkerType.setText("");
|
||||
viewBinding.markerEditDescription.setText("");
|
||||
} else {
|
||||
marker = new ContentProviderUtils(this).getMarker(markerId);
|
||||
if (marker == null) {
|
||||
Log.d(TAG, "marker is null");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
viewModel = new ViewModelProvider(this).get(MarkerEditViewModel.class);
|
||||
viewModel.getMarkerData(trackId, markerId).observe(this, data -> {
|
||||
marker = data;
|
||||
viewBinding.markerEditName.setText(marker.getName());
|
||||
viewBinding.markerEditMarkerType.setText(marker.getCategory());
|
||||
viewBinding.markerEditDescription.setText(marker.getDescription());
|
||||
if (marker.hasPhoto()) {
|
||||
photoUri = marker.getPhotoURI();
|
||||
setMarkerImageView(marker.getPhotoURI());
|
||||
} else {
|
||||
viewBinding.markerEditPhoto.setImageDrawable(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
photoUri = savedInstanceState.getParcelable(BUNDLE_PHOTO_URI);
|
||||
if (marker != null) {
|
||||
marker.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
||||
}
|
||||
}
|
||||
if (photoUri != null) {
|
||||
setMarkerImageView(photoUri);
|
||||
}
|
||||
|
||||
hideAndShowOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putParcelable(BUNDLE_PHOTO_URI, photoUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
hideAndShowOptions();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -227,16 +139,17 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.marker_edit_insert_photo:
|
||||
createMarkerWithPicture();
|
||||
return true;
|
||||
case R.id.marker_edit_insert_gallery_img:
|
||||
createMarkerWithGalleryImage();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
if (item.getItemId() == R.id.marker_edit_insert_photo) {
|
||||
createMarkerWithPicture();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.marker_edit_insert_gallery_img) {
|
||||
createMarkerWithGalleryImage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -246,26 +159,20 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
} else if (resultCode == RESULT_OK) {
|
||||
setMarkerImageView(photoUri);
|
||||
viewModel.onNewCameraPhoto(cameraPhotoUri,
|
||||
viewBinding.markerEditName.getText().toString(),
|
||||
viewBinding.markerEditMarkerType.getText().toString(),
|
||||
viewBinding.markerEditDescription.getText().toString());
|
||||
}
|
||||
} else if (requestCode == GALLERY_IMG_REQUEST_CODE) {
|
||||
if (resultCode == RESULT_CANCELED) {
|
||||
Toast.makeText(this, R.string.marker_add_photo_canceled, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
} else if (resultCode == RESULT_OK) {
|
||||
Uri srcUri = data.getData();
|
||||
try (ParcelFileDescriptor parcelFd = getContentResolver().openFileDescriptor(srcUri, "r")) {
|
||||
FileDescriptor srcFd = parcelFd.getFileDescriptor();
|
||||
File dstFile = new File(FileUtils.getImageUrl(this, getTrackId()));
|
||||
FileUtils.copy(srcFd, dstFile);
|
||||
|
||||
photoUri = FileUtils.getUriForFile(this, dstFile);
|
||||
setMarkerImageView(photoUri);
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
Toast.makeText(this, R.string.marker_add_canceled, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
} else if (resultCode == RESULT_OK) {
|
||||
viewModel.onNewGalleryPhoto(data.getData(),
|
||||
viewBinding.markerEditName.getText().toString(),
|
||||
viewBinding.markerEditMarkerType.getText().toString(),
|
||||
viewBinding.markerEditDescription.getText().toString());
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
@@ -273,11 +180,11 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
|
||||
/**
|
||||
* Checks and hide/shows all buttons/options about marker photo options.
|
||||
*
|
||||
* <p>
|
||||
* If a photo is set then one's options are shown, otherwise another ones are shown.
|
||||
*/
|
||||
private void hideAndShowOptions() {
|
||||
boolean isPhotoSet = (marker != null && marker.hasPhoto()) || photoUri != null;
|
||||
boolean isPhotoSet = (marker != null && marker.hasPhoto());
|
||||
if (insertPhotoMenuItem != null && insertGalleryImgMenuItem != null) {
|
||||
insertPhotoMenuItem.setVisible(!isPhotoSet);
|
||||
insertGalleryImgMenuItem.setVisible(!isPhotoSet);
|
||||
@@ -306,7 +213,7 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
|
||||
private void createMarkerWithPicture() {
|
||||
Pair<Intent, Uri> intentAndPhotoUri = IntentUtils.createTakePictureIntent(this, getTrackId());
|
||||
photoUri = intentAndPhotoUri.second;
|
||||
cameraPhotoUri = intentAndPhotoUri.second;
|
||||
startActivityForResult(intentAndPhotoUri.first, CAMERA_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@@ -314,21 +221,4 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
startActivityForResult(intent, GALLERY_IMG_REQUEST_CODE);
|
||||
}
|
||||
|
||||
private void addMarker() {
|
||||
trackRecordingServiceConnection.addMarker(this,
|
||||
viewBinding.markerEditName.getText().toString(),
|
||||
viewBinding.markerEditMarkerType.getText().toString(),
|
||||
viewBinding.markerEditDescription.getText().toString(),
|
||||
photoUri != null ? photoUri.toString() : null);
|
||||
}
|
||||
|
||||
private void saveMarker() {
|
||||
marker.setName(viewBinding.markerEditName.getText().toString());
|
||||
marker.setCategory(viewBinding.markerEditMarkerType.getText().toString());
|
||||
marker.setDescription(viewBinding.markerEditDescription.getText().toString());
|
||||
marker.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
|
||||
|
||||
new ContentProviderUtils(this).updateMarker(this, marker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
@@ -30,11 +29,12 @@ import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import de.dennisguse.opentracks.adapters.MarkerResourceCursorAdapter;
|
||||
import de.dennisguse.opentracks.adapters.ScrollVisibleViews;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -44,8 +44,6 @@ import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
@@ -81,7 +79,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
}
|
||||
};
|
||||
private Track track;
|
||||
private ResourceCursorAdapter resourceCursorAdapter;
|
||||
private MarkerResourceCursorAdapter resourceCursorAdapter;
|
||||
|
||||
private MarkerListBinding viewBinding;
|
||||
|
||||
@@ -93,14 +91,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
public void onPrepare(Menu menu, int[] positions, long[] ids, boolean showSelectAll) {
|
||||
boolean isSingleSelection = ids.length == 1;
|
||||
|
||||
menu.findItem(R.id.list_context_menu_share).setVisible(false);
|
||||
menu.findItem(R.id.list_context_menu_show_on_map).setVisible(isSingleSelection);
|
||||
menu.findItem(R.id.list_context_menu_edit).setVisible(isSingleSelection);
|
||||
menu.findItem(R.id.list_context_menu_delete).setVisible(true);
|
||||
/*
|
||||
* Set select all to the same visibility as delete since delete is the
|
||||
* only action that can be applied to multiple markers.
|
||||
*/
|
||||
|
||||
// Set select all to the same visibility as delete since delete is the only action that can be applied to multiple markers.
|
||||
menu.findItem(R.id.list_context_menu_select_all).setVisible(showSelectAll);
|
||||
}
|
||||
|
||||
@@ -126,33 +121,17 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
|
||||
viewBinding.markerList.setEmptyView(viewBinding.markerListEmpty);
|
||||
viewBinding.markerList.setOnItemClickListener((parent, view, position, id) -> {
|
||||
resourceCursorAdapter.markerInvalid(id);
|
||||
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
|
||||
.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(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 iconId = MarkerUtils.ICON_ID;
|
||||
String name = cursor.getString(nameIndex);
|
||||
long time = cursor.getLong(timeIndex);
|
||||
String category = cursor.getString(categoryIndex);
|
||||
String description = cursor.getString(descriptionIndex);
|
||||
String photoUrl = cursor.getString(photoUrlIndex);
|
||||
|
||||
ListItemUtils.setListItem(MarkerListActivity.this, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, photoUrl);
|
||||
}
|
||||
};
|
||||
resourceCursorAdapter = new MarkerResourceCursorAdapter(this, R.layout.list_item);
|
||||
ScrollVisibleViews scrollVisibleViews = new ScrollVisibleViews(resourceCursorAdapter);
|
||||
viewBinding.markerList.setOnScrollListener(scrollVisibleViews);
|
||||
viewBinding.markerList.setAdapter(resourceCursorAdapter);
|
||||
ActivityUtils.configureListViewContextualMenu(viewBinding.markerList, contextualActionModeCallback);
|
||||
|
||||
loadData(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,6 +145,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
this.invalidateOptionsMenu();
|
||||
loadData(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -174,6 +154,16 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
resourceCursorAdapter.clear();
|
||||
viewBinding = null;
|
||||
resourceCursorAdapter = null;
|
||||
contentProviderUtils = null;
|
||||
sharedPreferences = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = MarkerListBinding.inflate(getLayoutInflater());
|
||||
@@ -222,31 +212,45 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
markerIds[i] = new Marker.Id(longMarkerIds[i]);
|
||||
}
|
||||
|
||||
Intent intent;
|
||||
switch (itemId) {
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
if (markerIds.length == 1) {
|
||||
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getMarker(markerIds[0]));
|
||||
}
|
||||
return true;
|
||||
case R.id.list_context_menu_edit:
|
||||
if (markerIds.length == 1) {
|
||||
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
||||
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]);
|
||||
startActivity(intent);
|
||||
}
|
||||
return true;
|
||||
case R.id.list_context_menu_delete:
|
||||
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_select_all:
|
||||
for (int i = 0; i < viewBinding.markerList.getCount(); i++) {
|
||||
viewBinding.markerList.setItemChecked(i, true);
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
if (itemId == R.id.list_context_menu_show_on_map) {
|
||||
if (markerIds.length == 1) {
|
||||
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getMarker(markerIds[0]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_share) {
|
||||
Intent intent = IntentUtils.newShareFileIntent(this, markerIds);
|
||||
if (intent != null) {
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_edit) {
|
||||
if (markerIds.length == 1) {
|
||||
resourceCursorAdapter.markerInvalid(markerIds[0].getId());
|
||||
Intent intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
|
||||
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]);
|
||||
startActivity(intent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_delete) {
|
||||
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_select_all) {
|
||||
for (int i = 0; i < viewBinding.markerList.getCount(); i++) {
|
||||
viewBinding.markerList.setItemChecked(i, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,7 +23,8 @@ import android.os.Handler;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.databinding.TrackControllerBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
@@ -53,9 +54,8 @@ public class TrackController implements View.OnTouchListener {
|
||||
private final TransitionDrawable transition;
|
||||
|
||||
private boolean isRecording;
|
||||
private boolean isPaused;
|
||||
private long totalTime = 0;
|
||||
private long totalTimeTimestamp = 0;
|
||||
private boolean isRecordingPaused;
|
||||
private Duration totalTime;
|
||||
private boolean isResumed = false;
|
||||
|
||||
private final Callback callback;
|
||||
@@ -63,8 +63,9 @@ public class TrackController implements View.OnTouchListener {
|
||||
// A runnable to update the total time.
|
||||
private final Runnable updateTotalTimeRunnable = new Runnable() {
|
||||
public void run() {
|
||||
if (isResumed && isRecording && !isPaused) {
|
||||
viewBinding.trackControllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(System.currentTimeMillis() - totalTimeTimestamp + totalTime));
|
||||
if (isResumed && isRecording && !isRecordingPaused) {
|
||||
updateTotalTime();
|
||||
setTotalTime();
|
||||
handlerUpdateTotalTime.postDelayed(this, UnitConversions.ONE_SECOND_MS);
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ public class TrackController implements View.OnTouchListener {
|
||||
|
||||
viewBinding.trackControllerRecord.setOnTouchListener(this);
|
||||
viewBinding.trackControllerRecord.setOnClickListener((view) -> {
|
||||
if (buttonDelay != null || (isRecording && !isPaused)) {
|
||||
if (buttonDelay != null || (isRecording && !isRecordingPaused)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,7 +104,7 @@ public class TrackController implements View.OnTouchListener {
|
||||
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
||||
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
||||
buttonDelay = null;
|
||||
((ImageButton) view).setPressed(false);
|
||||
view.setPressed(false);
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
@@ -113,12 +114,12 @@ public class TrackController implements View.OnTouchListener {
|
||||
}
|
||||
|
||||
//Trigger button pressed animation
|
||||
((ImageButton) view).setPressed(true);
|
||||
view.setPressed(true);
|
||||
|
||||
//To pause a recording
|
||||
if (viewBinding.trackControllerRecord.equals(view)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& isRecording && !isPaused) {
|
||||
&& isRecording && !isRecordingPaused) {
|
||||
|
||||
transition.startTransition(buttonDelayDuration);
|
||||
|
||||
@@ -165,10 +166,11 @@ public class TrackController implements View.OnTouchListener {
|
||||
|
||||
public void update(boolean recording, boolean paused) {
|
||||
if (!isResumed) {
|
||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||
return;
|
||||
}
|
||||
isRecording = recording;
|
||||
isPaused = paused;
|
||||
isRecordingPaused = paused;
|
||||
boolean visible = alwaysShow || isRecording;
|
||||
viewBinding.trackControllerContainer.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
|
||||
@@ -177,26 +179,26 @@ public class TrackController implements View.OnTouchListener {
|
||||
return;
|
||||
}
|
||||
|
||||
viewBinding.trackControllerRecord.setImageResource(isRecording && !isPaused ? R.drawable.ic_button_pause : R.drawable.button_record);
|
||||
viewBinding.trackControllerRecord.setContentDescription(activity.getString(isRecording && !isPaused ? R.string.image_pause : R.string.image_record));
|
||||
if (isRecording && !isRecordingPaused) {
|
||||
handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UnitConversions.ONE_SECOND_MS);
|
||||
}
|
||||
|
||||
viewBinding.trackControllerRecord.setImageResource(isRecording && !isRecordingPaused ? R.drawable.ic_button_pause : R.drawable.button_record);
|
||||
viewBinding.trackControllerRecord.setContentDescription(activity.getString(isRecording && !isRecordingPaused ? R.string.image_pause : R.string.image_record));
|
||||
|
||||
viewBinding.trackControllerStop.setEnabled(isRecording);
|
||||
|
||||
viewBinding.trackControllerStatus.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
if (isRecording) {
|
||||
viewBinding.trackControllerStatus.setTextColor(activity.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text));
|
||||
viewBinding.trackControllerStatus.setTextColor(activity.getResources().getColor(isRecordingPaused ? android.R.color.white : R.color.recording_text));
|
||||
showStatusSetDefaultText();
|
||||
}
|
||||
|
||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||
viewBinding.trackControllerTotalTime.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
|
||||
if (isRecording) {
|
||||
totalTime = getTotalTime();
|
||||
viewBinding.trackControllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(totalTime));
|
||||
if (!isPaused) {
|
||||
totalTimeTimestamp = System.currentTimeMillis();
|
||||
handlerUpdateTotalTime.postDelayed(updateTotalTimeRunnable, UnitConversions.ONE_SECOND_MS);
|
||||
}
|
||||
updateTotalTime();
|
||||
setTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,16 +220,21 @@ public class TrackController implements View.OnTouchListener {
|
||||
viewBinding.trackControllerContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total time for the current recording track.
|
||||
*/
|
||||
private long getTotalTime() {
|
||||
private void updateTotalTime() {
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
return trackRecordingService != null ? trackRecordingService.getTotalTime() : 0L;
|
||||
if (trackRecordingService != null) {
|
||||
totalTime = trackRecordingService.getTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void setTotalTime() {
|
||||
if (totalTime != null) {
|
||||
viewBinding.trackControllerTotalTime.setText(StringUtils.formatElapsedTimeWithHour(totalTime));
|
||||
}
|
||||
}
|
||||
|
||||
private void showStatusSetDefaultText() {
|
||||
viewBinding.trackControllerStatus.setText(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
viewBinding.trackControllerStatus.setText(isRecordingPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
|
||||
@@ -78,6 +78,12 @@ public class TrackDeleteActivity extends AbstractActivity {
|
||||
deleteThread.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
viewBinding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = TrackDeleteBinding.inflate(getLayoutInflater());
|
||||
|
||||
@@ -58,7 +58,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection();
|
||||
Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||
if (!trackId.isValid()) {
|
||||
if (trackId == null) {
|
||||
Log.e(TAG, "invalid trackId");
|
||||
finish();
|
||||
return;
|
||||
|
||||
@@ -37,12 +37,15 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||
@@ -223,7 +226,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
String icon = cursor.getString(iconIndex);
|
||||
int iconId = TrackIconUtils.getIconDrawable(icon);
|
||||
String name = cursor.getString(nameIndex);
|
||||
String totalTime = StringUtils.formatElapsedTime(cursor.getLong(totalTimeIndex));
|
||||
String totalTime = StringUtils.formatElapsedTime(Duration.ofMillis(cursor.getLong(totalTimeIndex)));
|
||||
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, cursor.getDouble(totalDistanceIndex), metricUnits);
|
||||
int markerCount = cursor.getInt(markerCountIndex);
|
||||
long startTime = cursor.getLong(startTimeIndex);
|
||||
@@ -232,7 +235,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused,
|
||||
iconId, R.string.image_track, name, totalTime, totalDistance, markerCount,
|
||||
startTime, true, category, description, null);
|
||||
startTime, true, category, description, false);
|
||||
}
|
||||
};
|
||||
viewBinding.trackList.setAdapter(resourceCursorAdapter);
|
||||
@@ -243,6 +246,12 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
requestGPSPermissions();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupActionBarBack(Toolbar toolbar) {
|
||||
setSupportActionBar(toolbar);
|
||||
toolbar.setNavigationIcon(R.drawable.ic_logo_color_24dp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
@@ -320,43 +329,45 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
Intent intent;
|
||||
switch (item.getItemId()) {
|
||||
case R.id.track_list_start_gps:
|
||||
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
startActivity(intent);
|
||||
if (item.getItemId() == R.id.track_list_start_gps) {
|
||||
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||
} else {
|
||||
// Invoke trackRecordingService
|
||||
if (!isGpsStarted()) {
|
||||
trackRecordingServiceConnection.startAndBind(this);
|
||||
bindChangedCallback.run();
|
||||
} else {
|
||||
// Invoke trackRecordingService
|
||||
if (!isGpsStarted()) {
|
||||
trackRecordingServiceConnection.startAndBind(this);
|
||||
bindChangedCallback.run();
|
||||
} else {
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
trackRecordingService.stopGps();
|
||||
}
|
||||
trackRecordingServiceConnection.unbindAndStop(this);
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (trackRecordingService != null) {
|
||||
trackRecordingService.stopGps();
|
||||
}
|
||||
|
||||
// Update menu after starting or stopping gps
|
||||
this.invalidateOptionsMenu();
|
||||
trackRecordingServiceConnection.unbindAndStop(this);
|
||||
}
|
||||
return true;
|
||||
case R.id.track_list_aggregated_stats:
|
||||
intent = IntentUtils.newIntent(this, AggregatedStatisticsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_list_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_list_settings:
|
||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
// Update menu after starting or stopping gps
|
||||
this.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_list_aggregated_stats) {
|
||||
startActivity(IntentUtils.newIntent(this, AggregatedStatisticsActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_list_markers) {
|
||||
startActivity(IntentUtils.newIntent(this, MarkerListActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_list_settings) {
|
||||
startActivity(IntentUtils.newIntent(this, SettingsActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@@ -467,31 +478,37 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
trackIds[i] = new Track.Id(longTrackIds[i]);
|
||||
}
|
||||
|
||||
Intent intent;
|
||||
switch (itemId) {
|
||||
case R.id.list_context_menu_show_on_map:
|
||||
IntentDashboardUtils.startDashboard(this, false, trackIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.list_context_menu_edit:
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.list_context_menu_delete:
|
||||
deleteTracks(trackIds);
|
||||
return true;
|
||||
case R.id.list_context_menu_select_all:
|
||||
int size = viewBinding.trackList.getCount();
|
||||
for (int i = 0; i < size; i++) {
|
||||
viewBinding.trackList.setItemChecked(i, true);
|
||||
}
|
||||
return false;
|
||||
if (itemId == R.id.list_context_menu_show_on_map) {
|
||||
IntentDashboardUtils.startDashboard(this, false, trackIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_share) {
|
||||
Intent intent = IntentUtils.newShareFileIntent(this, trackIds);
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (itemId == R.id.list_context_menu_edit) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackIds[0]);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_delete) {
|
||||
deleteTracks(trackIds);
|
||||
return true;
|
||||
}
|
||||
if (itemId == R.id.list_context_menu_select_all) {
|
||||
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
||||
viewBinding.trackList.setItemChecked(i, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -147,43 +146,51 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
Intent intent;
|
||||
switch (item.getItemId()) {
|
||||
case R.id.track_detail_share:
|
||||
intent = IntentUtils.newShareFileIntent(this, trackId);
|
||||
intent = Intent.createChooser(intent, null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_menu_show_on_map:
|
||||
IntentDashboardUtils.startDashboard(this, false, trackId);
|
||||
return true;
|
||||
case R.id.track_detail_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_edit:
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_delete:
|
||||
deleteTracks(trackId);
|
||||
return true;
|
||||
case R.id.track_detail_resume_track:
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
finish();
|
||||
return true;
|
||||
case R.id.track_detail_settings:
|
||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
if (item.getItemId() == R.id.track_detail_share) {
|
||||
Intent intent = Intent.createChooser(IntentUtils.newShareFileIntent(this, trackId), null);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||
IntentDashboardUtils.startDashboard(this, false, trackId);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_markers) {
|
||||
Intent intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_edit) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_delete) {
|
||||
deleteTracks(trackId);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_resume_track) {
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_settings) {
|
||||
startActivity(IntentUtils.newIntent(this, SettingsActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -278,34 +278,41 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
Intent intent;
|
||||
switch (item.getItemId()) {
|
||||
case R.id.track_detail_insert_marker:
|
||||
intent = IntentUtils
|
||||
.newIntent(this, MarkerEditActivity.class)
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_menu_show_on_map:
|
||||
IntentDashboardUtils.startDashboard(this, true, trackId);
|
||||
return true;
|
||||
case R.id.track_detail_markers:
|
||||
intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_edit:
|
||||
intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
case R.id.track_detail_settings:
|
||||
intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
if (item.getItemId() == R.id.track_detail_insert_marker) {
|
||||
Intent intent = IntentUtils
|
||||
.newIntent(this, MarkerEditActivity.class)
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||
IntentDashboardUtils.startDashboard(this, true, trackId);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_markers) {
|
||||
Intent intent = IntentUtils.newIntent(this, MarkerListActivity.class)
|
||||
.putExtra(MarkerListActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_edit) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackEditActivity.class)
|
||||
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_settings) {
|
||||
Intent intent = IntentUtils.newIntent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,49 +46,45 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
AggregatedStatistics.AggregatedStatistic aggregatedStats = getItem(position);
|
||||
ViewHolder viewHolder;
|
||||
AggregatedStatistics.AggregatedStatistic aggregatedStatistic = getItem(position);
|
||||
|
||||
if (isSpeedSport(position)) {
|
||||
viewHolder = new ViewSpeedHolder();
|
||||
ViewHolder viewHolder;
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(context).inflate(R.layout.aggregated_stats_list_item, parent, false);
|
||||
viewHolder = new ViewHolder(convertView);
|
||||
convertView.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = new ViewPaceHolder();
|
||||
viewHolder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
convertView = LayoutInflater.from(context).inflate(R.layout.aggregated_stats_list_item, parent, false);
|
||||
viewHolder.setValues(convertView, getIcon(position), getName(position), aggregatedStats);
|
||||
if (TrackIconUtils.isSpeedIcon(context, aggregatedStatistic.getCategory())) {
|
||||
viewHolder.setSpeed(aggregatedStatistic);
|
||||
} else {
|
||||
viewHolder.setPace(aggregatedStatistic);
|
||||
}
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
private boolean isSpeedSport(int position) {
|
||||
String category = aggregatedStatistics.getSportName(position);
|
||||
return TrackIconUtils.isSpeedIcon(context, category);
|
||||
}
|
||||
|
||||
private int getIcon(int position) {
|
||||
String iconValue = TrackIconUtils.getIconValue(context, aggregatedStatistics.getSportName(position));
|
||||
return TrackIconUtils.getIconDrawable(iconValue);
|
||||
}
|
||||
|
||||
private String getName(int position) {
|
||||
return aggregatedStatistics.getSportName(position);
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
protected ImageView sportIcon;
|
||||
protected TextView typeLabel;
|
||||
protected TextView numTracks;
|
||||
protected TextView distance;
|
||||
protected TextView distanceUnit;
|
||||
protected TextView time;
|
||||
protected boolean metricsUnits;
|
||||
protected boolean reportSpeed;
|
||||
private final ImageView sportIcon;
|
||||
private final TextView typeLabel;
|
||||
private final TextView numTracks;
|
||||
private final TextView distance;
|
||||
private final TextView distanceUnit;
|
||||
private final TextView time;
|
||||
|
||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(context, name);
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
||||
private final TextView avgSpeed;
|
||||
private final TextView avgSpeedUnit;
|
||||
private final TextView avgSpeedLabel;
|
||||
private final TextView maxSpeed;
|
||||
private final TextView maxSpeedUnit;
|
||||
private final TextView maxSpeedLabel;
|
||||
|
||||
private boolean metricsUnits;
|
||||
private boolean reportSpeed;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
sportIcon = view.findViewById(R.id.aggregated_stats_sport_icon);
|
||||
typeLabel = view.findViewById(R.id.aggregated_stats_type_label);
|
||||
numTracks = view.findViewById(R.id.aggregated_stats_num_tracks);
|
||||
@@ -96,86 +92,68 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
||||
distanceUnit = view.findViewById(R.id.aggregated_stats_distance_unit);
|
||||
time = view.findViewById(R.id.aggregated_stats_time);
|
||||
|
||||
sportIcon.setImageResource(iconDrawable);
|
||||
typeLabel.setText(name);
|
||||
numTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStats.getCountTracks())));
|
||||
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(context, aggregatedStats.getTrackStatistics().getTotalDistance(), metricsUnits);
|
||||
distance.setText(parts.first);
|
||||
distanceUnit.setText(parts.second);
|
||||
|
||||
time.setText(StringUtils.formatElapsedTime(aggregatedStats.getTrackStatistics().getMovingTime()));
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewSpeedHolder extends ViewHolder {
|
||||
private TextView avgSpeed;
|
||||
private TextView avgSpeedUnit;
|
||||
private TextView avgSpeedLabel;
|
||||
private TextView maxSpeed;
|
||||
private TextView maxSpeedUnit;
|
||||
private TextView maxSpeedLabel;
|
||||
|
||||
@Override
|
||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||
super.setValues(view, iconDrawable, name, aggregatedStats);
|
||||
|
||||
avgSpeed = view.findViewById(R.id.aggregated_stats_avg_rate);
|
||||
avgSpeedUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
|
||||
avgSpeedLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
|
||||
maxSpeed = view.findViewById(R.id.aggregated_stats_max_rate);
|
||||
maxSpeedUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
|
||||
maxSpeedLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
||||
}
|
||||
|
||||
public void setSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
setCommonValues(aggregatedStatistic);
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||
avgSpeed.setText(parts.first);
|
||||
avgSpeedUnit.setText(parts.second);
|
||||
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_speed));
|
||||
}
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||
maxSpeed.setText(parts.first);
|
||||
maxSpeedUnit.setText(parts.second);
|
||||
maxSpeedLabel.setText(context.getString(R.string.stats_max_speed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewPaceHolder extends ViewHolder {
|
||||
private TextView avgPace;
|
||||
private TextView avgPaceUnit;
|
||||
private TextView avgPaceLabel;
|
||||
private TextView maxPace;
|
||||
private TextView maxPaceUnit;
|
||||
private TextView maxPaceLabel;
|
||||
|
||||
@Override
|
||||
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
|
||||
super.setValues(view, iconDrawable, name, aggregatedStats);
|
||||
|
||||
avgPace = view.findViewById(R.id.aggregated_stats_avg_rate);
|
||||
avgPaceUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
|
||||
avgPaceLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
|
||||
|
||||
maxPace = view.findViewById(R.id.aggregated_stats_max_rate);
|
||||
maxPaceUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
|
||||
maxPaceLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
|
||||
|
||||
public void setPace(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
setCommonValues(aggregatedStatistic);
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||
avgPace.setText(parts.first);
|
||||
avgPaceUnit.setText(parts.second);
|
||||
avgPaceLabel.setText(R.string.stats_average_moving_pace);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
|
||||
avgSpeed.setText(parts.first);
|
||||
avgSpeedUnit.setText(parts.second);
|
||||
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_pace));
|
||||
}
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||
maxPace.setText(parts.first);
|
||||
maxPaceUnit.setText(parts.second);
|
||||
maxPaceLabel.setText(R.string.stats_fastest_pace);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
|
||||
maxSpeed.setText(parts.first);
|
||||
maxSpeedUnit.setText(parts.second);
|
||||
maxSpeedLabel.setText(R.string.stats_fastest_pace);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
String category = aggregatedStatistic.getCategory();
|
||||
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(context, category);
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(context);
|
||||
|
||||
sportIcon.setImageResource(getIcon(aggregatedStatistic));
|
||||
typeLabel.setText(category);
|
||||
numTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStatistic.getCountTracks())));
|
||||
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(context, aggregatedStatistic.getTrackStatistics().getTotalDistance(), metricsUnits);
|
||||
distance.setText(parts.first);
|
||||
distanceUnit.setText(parts.second);
|
||||
|
||||
time.setText(StringUtils.formatElapsedTime(aggregatedStatistic.getTrackStatistics().getMovingTime()));
|
||||
}
|
||||
|
||||
private int getIcon(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
String iconValue = TrackIconUtils.getIconValue(context, aggregatedStatistic.getCategory());
|
||||
return TrackIconUtils.getIconDrawable(iconValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* ExecutorService wrap that avoid the execution of the same object in a ListView.
|
||||
*
|
||||
* @param <T> the type of the object that will be used to identified the Runnable.
|
||||
*/
|
||||
class ExecutorListViewService<T> {
|
||||
|
||||
private final List<T> enqueueObjects = new ArrayList<>();
|
||||
private final ExecutorService executorService;
|
||||
|
||||
public ExecutorListViewService(int numThreads) {
|
||||
executorService = Executors.newFixedThreadPool(numThreads);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
enqueueObjects.clear();
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the runnable for the object.
|
||||
*
|
||||
* @param object the Object.
|
||||
* @param runnable the Runnable.
|
||||
*/
|
||||
public void execute(T object, Runnable runnable) {
|
||||
if (!preExecute(object)) {
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
Future future = executorService.submit(runnable);
|
||||
try {
|
||||
future.get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
postExecute(object);
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Before execution it must checks if the object is already enqueued.
|
||||
*
|
||||
* @param object the object to check.
|
||||
* @return true if it can be executed or false otherwise.
|
||||
*/
|
||||
private boolean preExecute(T object) {
|
||||
synchronized (enqueueObjects) {
|
||||
if (!enqueueObjects.contains(object)) {
|
||||
enqueueObjects.add(object);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After execution remove the object from the queue.
|
||||
*
|
||||
* @param object the object to be removed from the queue.
|
||||
*/
|
||||
private void postExecute(T object) {
|
||||
synchronized (enqueueObjects) {
|
||||
enqueueObjects.remove(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
||||
|
||||
private final StackMode stackMode;
|
||||
private final boolean metricUnits;
|
||||
private float sumDistance_m;
|
||||
private final String category;
|
||||
|
||||
public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList, String category, StackMode stackMode) {
|
||||
@@ -46,12 +45,14 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
||||
viewHolder.distance = intervalView.findViewById(R.id.interval_item_distance);
|
||||
viewHolder.rate = intervalView.findViewById(R.id.interval_item_rate);
|
||||
viewHolder.gain = intervalView.findViewById(R.id.interval_item_gain);
|
||||
viewHolder.loss = intervalView.findViewById(R.id.interval_item_loss);
|
||||
|
||||
intervalView.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (ViewHolder) intervalView.getTag();
|
||||
}
|
||||
|
||||
float sumDistance_m;
|
||||
if (actualPosition + 1 == getCount() && actualPosition > 0) {
|
||||
sumDistance_m = actualPosition * getItem(actualPosition - 1).getDistance_m() + interval.getDistance_m();
|
||||
} else {
|
||||
@@ -66,6 +67,7 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
||||
}
|
||||
|
||||
viewHolder.gain.setText(StringUtils.formatDistance(getContext(), interval.getGain_m(), metricUnits));
|
||||
viewHolder.loss.setText(StringUtils.formatDistance(getContext(), interval.getLoss_m(), metricUnits));
|
||||
|
||||
return intervalView;
|
||||
}
|
||||
@@ -82,5 +84,6 @@ public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.I
|
||||
private TextView distance;
|
||||
private TextView rate;
|
||||
private TextView gain;
|
||||
private TextView loss;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.ThumbnailUtils;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.collection.LruCache;
|
||||
import androidx.cursoradapter.widget.ResourceCursorAdapter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
|
||||
public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implements ScrollVisibleViews.VisibleViewsListener {
|
||||
|
||||
private static final String TAG = MarkerResourceCursorAdapter.class.getSimpleName();
|
||||
|
||||
private static final int LIST_PREFERRED_ITEM_HEIGHT_DEFAULT = 128;
|
||||
|
||||
private final Activity activity;
|
||||
|
||||
//TODO Should be Marker.Id
|
||||
private final ExecutorListViewService executorService = new ExecutorListViewService<Long>(LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
|
||||
|
||||
private boolean scroll = false;
|
||||
|
||||
// Cache size is in bytes.
|
||||
private final LruCache<String, Bitmap> memoryCache;
|
||||
|
||||
public MarkerResourceCursorAdapter(Activity activity, int layout) {
|
||||
super(activity, layout, null, 0);
|
||||
|
||||
this.activity = activity;
|
||||
|
||||
// Get max available VM memory, exceeding this amount will throw an OutOfMemory exception.
|
||||
final long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
final int cacheSize = (int) (Runtime.getRuntime().maxMemory() - usedMemory) / 8;
|
||||
|
||||
memoryCache = new LruCache<String, Bitmap>(cacheSize) {
|
||||
@Override
|
||||
protected int sizeOf(@NonNull String key, @NonNull Bitmap bitmap) {
|
||||
return bitmap.getByteCount();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
int idIndex = cursor.getColumnIndex(MarkerColumns._ID);
|
||||
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);
|
||||
|
||||
long id = cursor.getLong(idIndex);
|
||||
int iconId = MarkerUtils.ICON_ID;
|
||||
String name = cursor.getString(nameIndex);
|
||||
long time = cursor.getLong(timeIndex);
|
||||
String category = cursor.getString(categoryIndex);
|
||||
String description = cursor.getString(descriptionIndex);
|
||||
String photoUrl = cursor.getString(photoUrlIndex);
|
||||
|
||||
view.setTag(String.valueOf(id));
|
||||
|
||||
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
|
||||
|
||||
ImageView imageView = view.findViewById(R.id.list_item_photo);
|
||||
ImageView textGradient = view.findViewById(R.id.list_item_text_gradient);
|
||||
imageView.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
||||
textGradient.setVisibility(hasPhoto ? View.VISIBLE : View.GONE);
|
||||
imageView.setImageBitmap(null);
|
||||
|
||||
if (hasPhoto) {
|
||||
int height = getPhotoHeight(activity);
|
||||
ViewGroup.LayoutParams params = imageView.getLayoutParams();
|
||||
params.height = height;
|
||||
imageView.setLayoutParams(params);
|
||||
|
||||
if (getBitmapFromMemCache(String.valueOf(id)) != null || !scroll) {
|
||||
asyncLoadPhoto(view, imageView, photoUrl, id);
|
||||
}
|
||||
}
|
||||
|
||||
ListItemUtils.setListItem(activity, view, false, true, iconId, R.string.image_marker, name, null, null, 0, time, false, category, description, hasPhoto);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewVisible(View view, int position) {
|
||||
scroll = true;
|
||||
|
||||
Cursor cursor = getCursor();
|
||||
if (!cursor.moveToPosition(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MarkerColumns._ID));
|
||||
String photoUrl = cursor.getString(cursor.getColumnIndexOrThrow(MarkerColumns.PHOTOURL));
|
||||
|
||||
boolean hasPhoto = photoUrl != null && !photoUrl.equals("");
|
||||
if (hasPhoto) {
|
||||
ImageView imageView = view.findViewById(R.id.list_item_photo);
|
||||
asyncLoadPhoto(view, imageView, photoUrl, id);
|
||||
}
|
||||
}
|
||||
|
||||
public void markerInvalid(long id) {
|
||||
memoryCache.remove(String.valueOf(id));
|
||||
scroll = false;
|
||||
}
|
||||
|
||||
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
|
||||
synchronized (memoryCache) {
|
||||
if (getBitmapFromMemCache(key) == null) {
|
||||
memoryCache.put(key, bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap getBitmapFromMemCache(String key) {
|
||||
return memoryCache.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* It loads the photoUrl in the imageView from view.
|
||||
* It takes the photo from cache or from storage if isn't in the cache.
|
||||
*
|
||||
* @param view item's view.
|
||||
* @param imageView view object where photo will be loaded.
|
||||
* @param photoUrl photo's url.
|
||||
* @param id marker's id where photo belong.
|
||||
*/
|
||||
private void asyncLoadPhoto(View view, ImageView imageView, String photoUrl, long id) {
|
||||
Bitmap photo = getBitmapFromMemCache(String.valueOf(id));
|
||||
imageView.setImageBitmap(photo);
|
||||
|
||||
if (photo == null) {
|
||||
executorService.execute(id, () -> {
|
||||
try (InputStream inputStream = activity.getContentResolver().openInputStream(Uri.parse(photoUrl))) {
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
||||
int height = getPhotoHeight(activity);
|
||||
Log.d(TAG, "Width : " + (bitmap.getWidth() / (bitmap.getHeight() / height)) + " | Height: " + height);
|
||||
final Bitmap finalPhoto = ThumbnailUtils.extractThumbnail(bitmap, bitmap.getWidth() / (bitmap.getHeight() / height), height);
|
||||
addBitmapToMemoryCache(String.valueOf(id), finalPhoto);
|
||||
if (view.getTag().equals(String.valueOf(id))) {
|
||||
activity.runOnUiThread(() -> imageView.setImageBitmap(finalPhoto));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to image " + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the photo height.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
private static int getPhotoHeight(Context context) {
|
||||
int[] attrs = new int[]{android.R.attr.listPreferredItemHeight};
|
||||
TypedArray typeArray = context.obtainStyledAttributes(attrs);
|
||||
int height = typeArray.getDimensionPixelSize(0, LIST_PREFERRED_ITEM_HEIGHT_DEFAULT);
|
||||
typeArray.recycle();
|
||||
return 2 * height;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.AbsListView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* AbsListView.OnScrollListener class that can be used to know what views in a ListView are currently visible while scrolling.
|
||||
*/
|
||||
public class ScrollVisibleViews implements AbsListView.OnScrollListener {
|
||||
private int from = -1;
|
||||
private int to = -1;
|
||||
|
||||
private final VisibleViewsListener visibleViewsListener;
|
||||
|
||||
public ScrollVisibleViews(@NonNull VisibleViewsListener visibleViewsListener) {
|
||||
this.visibleViewsListener = visibleViewsListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE || scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING || scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
|
||||
if (from >= 0 && to >= 0) {
|
||||
for (int i = from; i < to; i++) {
|
||||
View viewChild = view.getChildAt(i - from);
|
||||
visibleViewsListener.onViewVisible(viewChild, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
|
||||
from = firstVisibleItem;
|
||||
to = firstVisibleItem + visibleItemCount;
|
||||
}
|
||||
|
||||
interface VisibleViewsListener {
|
||||
void onViewVisible(View view, int position);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
@@ -13,19 +12,11 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorData;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
|
||||
public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
public static final int HEART_RATE_TYPE = 0;
|
||||
public static final int CADENCE_TYPE = 1;
|
||||
public static final int POWER_TYPE = 2;
|
||||
|
||||
private List<Pair<Integer, SensorData>> sensorDataList;
|
||||
private List<SensorDataModel> sensorDataList;
|
||||
private final Context context;
|
||||
|
||||
public SensorsAdapter(Context context) {
|
||||
@@ -42,9 +33,8 @@ public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SensorsAdapter.ViewHolder viewHolder = (SensorsAdapter.ViewHolder) holder;
|
||||
int type = sensorDataList.get(position).first;
|
||||
SensorData sensorData = sensorDataList.get(position).second;
|
||||
viewHolder.setData(sensorData, type);
|
||||
SensorDataModel sensorDataModel = sensorDataList.get(position);
|
||||
viewHolder.setData(sensorDataModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,12 +46,7 @@ public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return sensorDataList.get(position).first;
|
||||
}
|
||||
|
||||
public List<Pair<Integer, SensorData>> swapData(List<Pair<Integer, SensorData>> data) {
|
||||
public List<SensorDataModel> swapData(List<SensorDataModel> data) {
|
||||
if (sensorDataList == data) {
|
||||
return null;
|
||||
}
|
||||
@@ -76,10 +61,10 @@ public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView label;
|
||||
TextView sensorValue;
|
||||
TextView value;
|
||||
TextView unit;
|
||||
final TextView label;
|
||||
final TextView sensorValue;
|
||||
final TextView value;
|
||||
final TextView unit;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
@@ -89,70 +74,18 @@ public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
unit = itemView.findViewById(R.id.stats_sensor_unit);
|
||||
}
|
||||
|
||||
public void setData(SensorData sensorData, int type) {
|
||||
switch (type) {
|
||||
case HEART_RATE_TYPE:
|
||||
setHeartRateSensorData((SensorDataHeartRate) sensorData);
|
||||
break;
|
||||
case CADENCE_TYPE:
|
||||
setCadenceSensorData((SensorDataCycling.Cadence) sensorData);
|
||||
break;
|
||||
case POWER_TYPE:
|
||||
setPowerSensorData((SensorDataCyclingPower) sensorData);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unknown sensor type");
|
||||
}
|
||||
}
|
||||
public void setData(SensorDataModel sensorDataModel) {
|
||||
String sensorName = sensorDataModel.getSensorName();
|
||||
String sensorValue = sensorDataModel.hasValue() ? sensorDataModel.getSensorValue() : context.getString(R.string.value_unknown);
|
||||
|
||||
private void setHeartRateSensorData(SensorDataHeartRate data) {
|
||||
String sensorValue = context.getString(R.string.value_unknown);
|
||||
String sensorName = context.getString(R.string.value_unknown);
|
||||
if (data != null) {
|
||||
sensorName = data.getSensorNameOrAddress();
|
||||
if (data.hasHeartRate_bpm() && data.isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(data.getHeartRate_bpm(), 0);
|
||||
}
|
||||
this.label.setText(context.getString(sensorDataModel.getLabelId()));
|
||||
if (sensorName == null) {
|
||||
this.sensorValue.setVisibility(View.GONE);
|
||||
} else {
|
||||
this.sensorValue.setText(sensorName);
|
||||
}
|
||||
|
||||
this.label.setText(context.getString(R.string.sensor_state_heart_rate));
|
||||
this.sensorValue.setText(sensorName);
|
||||
this.value.setText(sensorValue);
|
||||
this.unit.setText(R.string.sensor_unit_beats_per_minute);
|
||||
}
|
||||
|
||||
private void setCadenceSensorData(SensorDataCycling.Cadence data) {
|
||||
String sensorValue = context.getString(R.string.value_unknown);
|
||||
String sensorName = context.getString(R.string.value_unknown);
|
||||
if (data != null) {
|
||||
sensorName = data.getSensorNameOrAddress();
|
||||
|
||||
if (data.hasCadence_rpm() && data.isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(data.getCadence_rpm(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
this.label.setText(context.getString(R.string.sensor_state_cadence));
|
||||
this.sensorValue.setText(sensorName);
|
||||
this.value.setText(sensorValue);
|
||||
this.unit.setText(R.string.sensor_unit_rounds_per_minute);
|
||||
}
|
||||
|
||||
private void setPowerSensorData(SensorDataCyclingPower data) {
|
||||
String sensorValue = context.getString(R.string.value_unknown);
|
||||
String sensorName = context.getString(R.string.value_unknown);
|
||||
if (data != null) {
|
||||
sensorName = data.getSensorNameOrAddress();
|
||||
|
||||
if (data.hasPower_w() && data.isRecent()) {
|
||||
sensorValue = StringUtils.formatDecimal(data.getPower_w(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
this.label.setText(context.getString(R.string.sensor_state_power));
|
||||
this.sensorValue.setText(sensorName);
|
||||
this.value.setText(sensorValue);
|
||||
this.unit.setText(R.string.sensor_unit_power);
|
||||
this.unit.setText(context.getString(sensorDataModel.getUnitId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ChartPoint {
|
||||
}
|
||||
timeOrDistance = distance;
|
||||
} else {
|
||||
timeOrDistance = trackStatistics.getTotalTime();
|
||||
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
||||
}
|
||||
|
||||
elevation = trackStatisticsUpdater.getSmoothedElevation();
|
||||
|
||||
@@ -38,6 +38,7 @@ import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GestureDetectorCompat;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -740,7 +741,7 @@ public class ChartView extends View {
|
||||
* @param spacing the spacing between x axis and marker
|
||||
*/
|
||||
private void drawXAxisMarker(Canvas canvas, double value, NumberFormat numberFormat, int spacing) {
|
||||
String marker = chartByDistance ? numberFormat.format(value) : StringUtils.formatElapsedTime((long) value);
|
||||
String marker = chartByDistance ? numberFormat.format(value) : StringUtils.formatElapsedTime((Duration.ofMillis((long) value)));
|
||||
Rect rect = getRect(xAxisMarkerPaint, marker);
|
||||
canvas.drawText(marker, getX(value), topBorder + effectiveHeight + spacing + rect.height(), xAxisMarkerPaint);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.util.Pair;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -140,8 +142,13 @@ public class DescriptionGenerator {
|
||||
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
||||
}
|
||||
|
||||
// Elevation loss
|
||||
if (stats.hasTotalElevationLoss()) {
|
||||
writeElevation(stats.getTotalElevationLoss(), builder, R.string.description_elevation_loss, lineBreak);
|
||||
}
|
||||
|
||||
// Recorded time
|
||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime_ms())));
|
||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime())));
|
||||
builder.append(lineBreak);
|
||||
|
||||
return builder.toString();
|
||||
@@ -172,7 +179,7 @@ public class DescriptionGenerator {
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeTime(long time, StringBuilder builder, int resId, String lineBreak) {
|
||||
void writeTime(Duration time, StringBuilder builder, int resId, String lineBreak) {
|
||||
builder.append(context.getString(resId, StringUtils.formatElapsedTime(time)));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
|
||||
@@ -40,13 +40,12 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Track data hub.
|
||||
* Receives data from {@link de.dennisguse.opentracks.content.provider.CustomContentProvider} and distributes it to {@link TrackDataListener} after some processing.
|
||||
*
|
||||
* <p>
|
||||
* {@link TrackPoint}s are filtered/downsampled with a dynamic sampling frequency.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
@@ -83,8 +82,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
|
||||
// Track points sampling state
|
||||
private int numLoadedPoints;
|
||||
private long firstSeenTrackPointId;
|
||||
private long lastSeenTrackPointId;
|
||||
private TrackPoint.Id firstSeenTrackPointId;
|
||||
private TrackPoint.Id lastSeenTrackPointId;
|
||||
|
||||
// Registered listeners
|
||||
private ContentObserver tracksTableObserver;
|
||||
@@ -143,7 +142,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
|
||||
PreferencesUtils.register(context, this);
|
||||
onSharedPreferenceChanged(null, null);
|
||||
runInHandlerThread(() -> {
|
||||
handler.post(() -> {
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
}
|
||||
@@ -174,7 +173,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
}
|
||||
|
||||
public void loadTrack(final @NonNull Track.Id trackId) {
|
||||
runInHandlerThread(() -> {
|
||||
handler.post(() -> {
|
||||
if (trackId.equals(selectedTrackId)) {
|
||||
Log.i(TAG, "Not reloading track " + trackId.getId());
|
||||
return;
|
||||
@@ -190,7 +189,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
* @param trackDataListener the track data listener
|
||||
*/
|
||||
public void registerTrackDataListener(final TrackDataListener trackDataListener, final boolean tracksTable, final boolean markersTable, final boolean trackPointsTable_SampleIn, final boolean trackPointsTable_SampleOut) {
|
||||
runInHandlerThread(() -> {
|
||||
handler.post(() -> {
|
||||
trackDataManager.registerTrackDataListener(trackDataListener, tracksTable, markersTable, trackPointsTable_SampleIn, trackPointsTable_SampleOut);
|
||||
if (started) {
|
||||
loadDataForListener(trackDataListener);
|
||||
@@ -204,7 +203,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
* @param trackDataListener the track data listener
|
||||
*/
|
||||
public void unregisterTrackDataListener(final TrackDataListener trackDataListener) {
|
||||
runInHandlerThread(() -> trackDataManager.unregisterTrackDataListener(trackDataListener));
|
||||
handler.post(() -> trackDataManager.unregisterTrackDataListener(trackDataListener));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,7 +222,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
|
||||
runInHandlerThread(() -> {
|
||||
handler.post(() -> {
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(context);
|
||||
}
|
||||
@@ -314,9 +313,6 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
do {
|
||||
Marker marker = contentProviderUtils.createMarker(cursor);
|
||||
if (!LocationUtils.isValidLocation(marker.getLocation())) {
|
||||
continue;
|
||||
}
|
||||
for (TrackDataListener trackDataListener : trackDataListeners) {
|
||||
trackDataListener.onNewMarker(marker);
|
||||
}
|
||||
@@ -351,68 +347,75 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
}
|
||||
|
||||
int localNumLoadedTrackPoints = updateSamplingState ? numLoadedPoints : 0;
|
||||
long localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : -1L;
|
||||
long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L;
|
||||
long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId;
|
||||
TrackPoint.Id localFirstSeenTrackPointId = updateSamplingState ? firstSeenTrackPointId : null;
|
||||
TrackPoint.Id localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : null;
|
||||
TrackPoint.Id maxPointId = updateSamplingState ? null : lastSeenTrackPointId;
|
||||
|
||||
if (selectedTrackId == null) {
|
||||
Log.w(TAG, "This should not happen, but it does"); //TODO
|
||||
return;
|
||||
}
|
||||
|
||||
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
|
||||
TrackPoint.Id lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
|
||||
int samplingFrequency = -1;
|
||||
boolean includeNextPoint = false;
|
||||
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, localLastSeenTrackPointIdId + 1, false)) {
|
||||
|
||||
TrackPoint.Id next = null;
|
||||
if (localLastSeenTrackPointIdId != null) {
|
||||
next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = null;
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
|
||||
|
||||
while (trackPointIterator.hasNext()) {
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
long trackPointId = trackPointIterator.getTrackPointId();
|
||||
trackPoint = trackPointIterator.next();
|
||||
TrackPoint.Id trackPointId = trackPoint.getId();
|
||||
|
||||
// Stop if past the last wanted point
|
||||
if (maxPointId != -1L && trackPointId > maxPointId) {
|
||||
if (maxPointId != null && trackPointId.getId() > maxPointId.getId()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (localFirstSeenTrackPointId == -1) {
|
||||
if (localFirstSeenTrackPointId == null) {
|
||||
localFirstSeenTrackPointId = trackPointId;
|
||||
}
|
||||
|
||||
if (samplingFrequency == -1) {
|
||||
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenTrackPointId);
|
||||
long numTotalPoints = Math.max(0L, lastTrackPointId.getId() - localFirstSeenTrackPointId.getId()); //TODO That is an assumption; should be derived from the DB.
|
||||
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
||||
}
|
||||
|
||||
if (!LocationUtils.isValidLocation(trackPoint.getLocation())) { //This can be split markers (not anymore supported feature)
|
||||
includeNextPoint = true;
|
||||
|
||||
// Also include the last point if the selected track is not recording.
|
||||
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
||||
trackDataListener.onSampledInTrackPoint(trackPoint);
|
||||
}
|
||||
} else {
|
||||
// Also include the last point if the selected track is not recording.
|
||||
if (includeNextPoint || (localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
includeNextPoint = false;
|
||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
||||
trackDataListener.onSampledInTrackPoint(trackPoint);
|
||||
}
|
||||
} else {
|
||||
for (TrackDataListener trackDataListener : sampledOutListeners) {
|
||||
trackDataListener.onSampledOutTrackPoint(trackPoint);
|
||||
}
|
||||
for (TrackDataListener trackDataListener : sampledOutListeners) {
|
||||
trackDataListener.onSampledOutTrackPoint(trackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
localNumLoadedTrackPoints++;
|
||||
localLastSeenTrackPointIdId = trackPointId;
|
||||
}
|
||||
}
|
||||
|
||||
if (trackPoint != null) {
|
||||
localLastSeenTrackPointIdId = trackPoint.getId();
|
||||
}
|
||||
|
||||
if (updateSamplingState) {
|
||||
numLoadedPoints = localNumLoadedTrackPoints;
|
||||
firstSeenTrackPointId = localFirstSeenTrackPointId;
|
||||
lastSeenTrackPointId = localLastSeenTrackPointIdId;
|
||||
}
|
||||
|
||||
for (TrackDataListener listener : sampledInListeners) {
|
||||
listener.onNewTrackPointsDone();
|
||||
if (trackPoint != null) {
|
||||
for (TrackDataListener listener : sampledInListeners) {
|
||||
listener.onNewTrackPointsDone(trackPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,23 +424,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
|
||||
*/
|
||||
private void resetSamplingState() {
|
||||
numLoadedPoints = 0;
|
||||
firstSeenTrackPointId = -1L;
|
||||
lastSeenTrackPointId = -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run in the handler thread.
|
||||
*
|
||||
* @param runnable the runnable
|
||||
*/
|
||||
@Deprecated //TODO: Why actually catch this problem: I guess it would be better to fail hard.
|
||||
@VisibleForTesting
|
||||
private void runInHandlerThread(Runnable runnable) {
|
||||
if (handler == null) {
|
||||
// Use a Throwable to ensure the stack trace is logged.
|
||||
Log.d(TAG, "handler is null.", new Throwable());
|
||||
return;
|
||||
}
|
||||
handler.post(runnable);
|
||||
firstSeenTrackPointId = null;
|
||||
lastSeenTrackPointId = null;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package de.dennisguse.opentracks.content;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -32,6 +34,7 @@ public interface TrackDataListener {
|
||||
*
|
||||
* @param track the track
|
||||
*/
|
||||
//TODO Could be @NonNull
|
||||
void onTrackUpdated(Track track);
|
||||
|
||||
/**
|
||||
@@ -44,20 +47,20 @@ public interface TrackDataListener {
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledInTrackPoint(TrackPoint trackPoint);
|
||||
void onSampledInTrackPoint(@NonNull TrackPoint trackPoint);
|
||||
|
||||
/**
|
||||
* Called when a sampled out track point is read.
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledOutTrackPoint(TrackPoint trackPoint);
|
||||
void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint);
|
||||
|
||||
/**
|
||||
* Called when finish sending new track points.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(TrackPoint)} and {@link #onSampledOutTrackPoint(TrackPoint)}.
|
||||
*/
|
||||
void onNewTrackPointsDone();
|
||||
void onNewTrackPointsDone(@NonNull TrackPoint lastTrackPoint);
|
||||
|
||||
/**
|
||||
* Called to clear previously sent markers.
|
||||
|
||||
@@ -23,13 +23,13 @@ import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* NOTE: A marker is indirectly (via it's location) assigned to one {@link TrackPoint} with trackPoint.hasLocation() == true.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
@@ -41,46 +41,57 @@ public final class Marker {
|
||||
private String category = "";
|
||||
private String icon = "";
|
||||
private final Track.Id trackId;
|
||||
|
||||
private final Instant time;
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private Float accuracy;
|
||||
private Double altitude_m;
|
||||
private Float bearing;
|
||||
|
||||
//TODO It is the distance from the track starting point; rename to something more meaningful
|
||||
private double length = 0.0;
|
||||
private long duration = 0;
|
||||
private final Location location;
|
||||
private long duration = 0; //TODO Duration
|
||||
|
||||
@Deprecated //TODO Make an URI instead of String
|
||||
private String photoUrl = "";
|
||||
|
||||
@VisibleForTesting
|
||||
public Marker(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
||||
this(trackId, trackPoint.getLocation());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO Used by AbstractFileImporter to create an intermediate marker before saving a new one into the database.
|
||||
public Marker(@NonNull Location location) {
|
||||
this(null, location);
|
||||
}
|
||||
|
||||
public Marker(@NonNull Track.Id trackId, @NonNull Location location) {
|
||||
this.location = location;
|
||||
public Marker(@Nullable Track.Id trackId, Instant time) {
|
||||
this.trackId = trackId;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, double length, long duration, @NonNull Location location, String photoUrl) {
|
||||
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
||||
this.trackId = trackId;
|
||||
|
||||
this.time = trackPoint.getTime();
|
||||
|
||||
if (!trackPoint.hasLocation())
|
||||
throw new RuntimeException("Marker requires a trackpoint with a location.");
|
||||
|
||||
this.latitude = trackPoint.getLatitude();
|
||||
this.longitude = trackPoint.getLongitude();
|
||||
if (trackPoint.hasAccuracy()) this.accuracy = trackPoint.getAccuracy();
|
||||
if (trackPoint.hasAltitude()) this.altitude_m = trackPoint.getAltitude();
|
||||
if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing();
|
||||
}
|
||||
|
||||
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, double length, long duration, @NonNull TrackPoint trackPoint, String photoUrl) {
|
||||
this(trackId, trackPoint);
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.category = category;
|
||||
this.icon = icon;
|
||||
this.trackId = trackId;
|
||||
this.length = length;
|
||||
this.duration = duration;
|
||||
this.location = location;
|
||||
this.photoUrl = photoUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* May be null if the it was not loaded from the database.
|
||||
*/
|
||||
public @Nullable
|
||||
Id getId() {
|
||||
@Nullable
|
||||
public Id getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -88,6 +99,10 @@ public final class Marker {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Instant getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -120,11 +135,88 @@ public final class Marker {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public @NonNull
|
||||
Track.Id getTrackId() {
|
||||
@NonNull
|
||||
public Track.Id getTrackId() {
|
||||
return trackId;
|
||||
}
|
||||
|
||||
public boolean hasLocation() {
|
||||
return latitude != null || longitude != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Location getLocation() {
|
||||
Location location = new Location("");
|
||||
location.setTime(time.toEpochMilli());
|
||||
if (hasLocation()) {
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
}
|
||||
if (hasBearing()) {
|
||||
location.setBearing(bearing);
|
||||
}
|
||||
if (hasAccuracy()) {
|
||||
location.setAccuracy(accuracy);
|
||||
}
|
||||
if (hasAltitude()) {
|
||||
location.setAltitude(altitude_m);
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public boolean hasAccuracy() {
|
||||
return accuracy != null;
|
||||
}
|
||||
|
||||
public Float getAccuracy() {
|
||||
return accuracy;
|
||||
}
|
||||
|
||||
public void setAccuracy(Float accuracy) {
|
||||
this.accuracy = accuracy;
|
||||
}
|
||||
|
||||
public boolean hasAltitude() {
|
||||
return altitude_m != null;
|
||||
}
|
||||
|
||||
public Double getAltitude() {
|
||||
return altitude_m;
|
||||
}
|
||||
|
||||
public void setAltitude(double altitude_m) {
|
||||
this.altitude_m = altitude_m;
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
return bearing != null;
|
||||
}
|
||||
|
||||
public Float getBearing() {
|
||||
return bearing;
|
||||
}
|
||||
|
||||
public void setBearing(float bearing) {
|
||||
this.bearing = bearing;
|
||||
}
|
||||
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
@@ -141,11 +233,6 @@ public final class Marker {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public @NonNull
|
||||
Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public String getPhotoUrl() {
|
||||
return photoUrl;
|
||||
}
|
||||
@@ -175,11 +262,6 @@ public final class Marker {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Deprecated //TODO Use a 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;
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* A track.
|
||||
@@ -53,8 +52,8 @@ public class Track {
|
||||
/**
|
||||
* May be null if the track was not loaded from the database.
|
||||
*/
|
||||
public @Nullable
|
||||
Id getId() {
|
||||
@Nullable
|
||||
public Id getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -127,11 +126,6 @@ public class Track {
|
||||
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;
|
||||
|
||||
@@ -16,82 +16,207 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This class extends the standard Android location with extra information.
|
||||
* <p>
|
||||
* NOTE: default location will be latitude=0.0 and longitude=0.0.
|
||||
* For {@link TrackPoint}s with `type == null` this is not meaningful.
|
||||
* <p>
|
||||
* NOTE: For Locations provided by the GPS.
|
||||
* We are replacing the GPS-provided time using the system time.
|
||||
* Then we have the same timestamps for the user-driven events (aka start, pause, resume) and restore {@link de.dennisguse.opentracks.stats.TrackStatistics}.
|
||||
* Drawbacks:
|
||||
* * GPS-provided timestamp might be more precise (but also have GPS week rollover)
|
||||
* * System clock might be changed (and thus non-monotonic)
|
||||
* TODO: if these might be a problem, we need to store both timestamps.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class TrackPoint {
|
||||
|
||||
private final Location location;
|
||||
private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1);
|
||||
|
||||
// Anything faster than that (in meters per second) will be considered moving.
|
||||
private static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
||||
|
||||
private TrackPoint.Id id;
|
||||
|
||||
private Instant time;
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private Float accuracy;
|
||||
private Double altitude_m;
|
||||
private Float speed_mps;
|
||||
private Float bearing;
|
||||
|
||||
public enum Type {
|
||||
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
|
||||
|
||||
SEGMENT_START_AUTOMATIC(-1), //Start of a segment due to too much distance from previous TrackPoint
|
||||
TRACKPOINT(0), //Just GPS data.
|
||||
|
||||
SEGMENT_END_MANUAL(1); //End of a segment
|
||||
|
||||
public final int type_db;
|
||||
|
||||
Type(int type_db) {
|
||||
this.type_db = type_db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name() + "(" + type_db + ")";
|
||||
}
|
||||
|
||||
public static Type getById(int id) {
|
||||
for (Type e : values()) {
|
||||
if (e.type_db == id) return e;
|
||||
}
|
||||
|
||||
throw new RuntimeException("unknown id: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Type type;
|
||||
|
||||
private Float heartRate_bpm = null;
|
||||
private Float cyclingCadence_rpm = null;
|
||||
private Float power = null;
|
||||
private Float elevationGain = null;
|
||||
private Float elevationLoss = null;
|
||||
|
||||
public TrackPoint() {
|
||||
this.location = new Location("");
|
||||
public TrackPoint(@NonNull Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull Location location) {
|
||||
this.location = location;
|
||||
this(Type.TRACKPOINT);
|
||||
|
||||
this.latitude = location.getLatitude();
|
||||
this.longitude = location.getLongitude();
|
||||
this.altitude_m = location.getAltitude();
|
||||
this.speed_mps = location.getSpeed();
|
||||
this.accuracy = location.getAccuracy();
|
||||
|
||||
setTime(Instant.now());
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
this.location = trackPoint.getLocation();
|
||||
|
||||
this.heartRate_bpm = trackPoint.getHeartRate_bpm();
|
||||
this.cyclingCadence_rpm = trackPoint.getCyclingCadence_rpm();
|
||||
this.power = trackPoint.getPower();
|
||||
|
||||
this.elevationGain = trackPoint.getElevationGain();
|
||||
public TrackPoint(@NonNull Type type, Instant time) {
|
||||
this(type);
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public TrackPoint(double latitude, double longitude, Double altitude, long time) {
|
||||
location = new Location(LocationManager.GPS_PROVIDER);
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
if (altitude != null) {
|
||||
location.setAltitude(altitude);
|
||||
public TrackPoint(double latitude, double longitude, Double altitude, Instant time) {
|
||||
this(Type.TRACKPOINT);
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
this.altitude_m = altitude;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Deprecated //See #316
|
||||
public static TrackPoint createSegmentStartManual() {
|
||||
return createSegmentStartManualWithTime(Instant.now());
|
||||
}
|
||||
|
||||
public static TrackPoint createSegmentStartManualWithTime(Instant time) {
|
||||
return new TrackPoint(Type.SEGMENT_START_MANUAL, time);
|
||||
}
|
||||
|
||||
@Deprecated //See #316
|
||||
public static TrackPoint createSegmentStartAutomatic() {
|
||||
return createSegmentStartAutomaticWithTime(Instant.now());
|
||||
}
|
||||
|
||||
public static TrackPoint createSegmentStartAutomaticWithTime(Instant time) {
|
||||
return new TrackPoint(Type.SEGMENT_START_AUTOMATIC, time);
|
||||
}
|
||||
|
||||
public static TrackPoint createSegmentEnd() {
|
||||
return createSegmentEndWithTime(Instant.now());
|
||||
}
|
||||
|
||||
public static TrackPoint createSegmentEndWithTime(Instant time) {
|
||||
return new TrackPoint(Type.SEGMENT_END_MANUAL, time);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(@NonNull Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public boolean isSegmentStart() {
|
||||
return type == Type.SEGMENT_START_AUTOMATIC || type == Type.SEGMENT_START_MANUAL;
|
||||
}
|
||||
|
||||
public boolean isSegmentEnd() {
|
||||
return type == Type.SEGMENT_END_MANUAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* May be null if the track was not loaded from the database.
|
||||
*/
|
||||
@Nullable
|
||||
public TrackPoint.Id getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(TrackPoint.Id id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public boolean hasLocation() {
|
||||
return latitude != null && longitude != null;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Location getLocation() {
|
||||
Location location = new Location("");
|
||||
location.setTime(time.toEpochMilli());
|
||||
if (hasLocation()) {
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
}
|
||||
if (hasBearing()) {
|
||||
location.setBearing(bearing);
|
||||
}
|
||||
if (hasAccuracy()) {
|
||||
location.setAccuracy(accuracy);
|
||||
}
|
||||
if (hasAltitude()) {
|
||||
location.setAltitude(altitude_m);
|
||||
}
|
||||
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);
|
||||
pause.setLatitude(TrackPointsColumns.PAUSE_LATITUDE);
|
||||
pause.setTime(time);
|
||||
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);
|
||||
resume.setLatitude(TrackPointsColumns.RESUME_LATITUDE);
|
||||
resume.setTime(time);
|
||||
return new TrackPoint(resume);
|
||||
}
|
||||
|
||||
public @NonNull
|
||||
Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@@ -99,7 +224,7 @@ public class TrackPoint {
|
||||
return elevationGain != null;
|
||||
}
|
||||
|
||||
public float getElevationGain() {
|
||||
public Float getElevationGain() {
|
||||
return elevationGain;
|
||||
}
|
||||
|
||||
@@ -107,88 +232,98 @@ public class TrackPoint {
|
||||
this.elevationGain = elevationGain;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return location.getLatitude();
|
||||
public boolean hasElevationLoss() {
|
||||
return elevationLoss != null;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
location.setLatitude(latitude);
|
||||
public float getElevationLoss() {
|
||||
return elevationLoss;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return location.getLongitude();
|
||||
public void setElevationLoss(Float elevationLoss) {
|
||||
this.elevationLoss = elevationLoss;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
location.setLongitude(longitude);
|
||||
public Instant getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return location.getTime();
|
||||
public void setTime(Instant time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
location.setTime(time);
|
||||
public boolean isRecent() {
|
||||
return Instant.now()
|
||||
.isBefore(time.plus(MAX_LOCATION_AGE));
|
||||
}
|
||||
|
||||
|
||||
public boolean hasAltitude() {
|
||||
return location.hasAltitude();
|
||||
return altitude_m != null;
|
||||
}
|
||||
|
||||
public double getAltitude() {
|
||||
return location.getAltitude();
|
||||
return altitude_m;
|
||||
}
|
||||
|
||||
public void setAltitude(double altitude) {
|
||||
location.setAltitude(altitude);
|
||||
this.altitude_m = altitude;
|
||||
}
|
||||
|
||||
public boolean hasSpeed() {
|
||||
return location.hasSpeed();
|
||||
return speed_mps != null;
|
||||
}
|
||||
|
||||
public float getSpeed() {
|
||||
return location.getSpeed();
|
||||
return speed_mps;
|
||||
}
|
||||
|
||||
public void setSpeed(float speed) {
|
||||
location.setSpeed(speed);
|
||||
public void setSpeed(Float speed) {
|
||||
this.speed_mps = speed;
|
||||
}
|
||||
|
||||
public boolean isMoving() {
|
||||
return hasSpeed() && getSpeed() >= MAX_NO_MOVEMENT_SPEED;
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
return location.hasBearing();
|
||||
return bearing != null;
|
||||
}
|
||||
|
||||
public float getBearing() {
|
||||
return location.getBearing();
|
||||
return bearing;
|
||||
}
|
||||
|
||||
public void setBearing(float bearing) {
|
||||
location.setBearing(bearing);
|
||||
public void setBearing(Float bearing) {
|
||||
this.bearing = bearing;
|
||||
}
|
||||
|
||||
public boolean hasAccuracy() {
|
||||
return location.hasAccuracy();
|
||||
return accuracy != null;
|
||||
}
|
||||
|
||||
public float getAccuracy() {
|
||||
return location.getAccuracy();
|
||||
return accuracy;
|
||||
}
|
||||
|
||||
public void setAccuracy(float horizontalAccuracy) {
|
||||
location.setAccuracy(horizontalAccuracy);
|
||||
this.accuracy = horizontalAccuracy;
|
||||
}
|
||||
|
||||
public float distanceTo(@NonNull TrackPoint dest) {
|
||||
return location.distanceTo(dest.getLocation());
|
||||
return getLocation().distanceTo(dest.getLocation());
|
||||
}
|
||||
|
||||
public boolean fulfillsAccuracy(int poorAccuracy) {
|
||||
return hasAccuracy() && accuracy < poorAccuracy;
|
||||
}
|
||||
|
||||
public float bearingTo(@NonNull TrackPoint dest) {
|
||||
return location.bearingTo(dest.getLocation());
|
||||
return getLocation().bearingTo(dest.getLocation());
|
||||
}
|
||||
|
||||
public float bearingTo(@NonNull Location dest) {
|
||||
return location.bearingTo(dest);
|
||||
return getLocation().bearingTo(dest);
|
||||
}
|
||||
|
||||
// Sensor data
|
||||
@@ -235,6 +370,52 @@ public class TrackPoint {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "time=" + getTime() + ": lat=" + getLatitude() + " lng=" + getLongitude() + " acc=" + getAccuracy();
|
||||
String result = "time=" + getTime() + " (type=" + getType() + ")";
|
||||
if (!hasLocation()) {
|
||||
return result;
|
||||
}
|
||||
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
|
||||
if (!hasAccuracy()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return result + " acc=" + getAccuracy();
|
||||
}
|
||||
|
||||
public static class Id {
|
||||
|
||||
private final long id;
|
||||
|
||||
public Id(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
protected Id(Parcel in) {
|
||||
id = in.readLong();
|
||||
}
|
||||
|
||||
//TOOD Limit visibility to TrackRecordingService / ContentProvider
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
TrackPoint.Id id1 = (TrackPoint.Id) o;
|
||||
return id == id1.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||