Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b30ecabb1 | |||
| 107cc14a2d | |||
| 6d3d8bb83c | |||
| 9c8e0335ef | |||
| 8c648c69e8 | |||
| 4422464f8d | |||
| d094531779 | |||
| a01341d235 | |||
| 83065dad39 | |||
| 68ba3e0c2a | |||
| 3f21edc44c | |||
| aefd1a6a35 | |||
| 74fa73e4a4 | |||
| 0a006d64f9 | |||
| 997ab6060e | |||
| 6d926ca7a8 | |||
| 3c1f1a9ba5 | |||
| ed5da55fba | |||
| d1e9649505 | |||
| 2431d60171 | |||
| e08f5e7273 | |||
| 7d2a6fff59 | |||
| a5158a55d9 | |||
| 15fbd90ca4 | |||
| eebc33f60c | |||
| ba2a755fb7 | |||
| 344c50a20c | |||
| 9a9a610321 | |||
| 8af427ea48 | |||
| 7b7d7c8061 | |||
| 7d6919d3f9 | |||
| 7eceb979c3 | |||
| bd48944ad5 | |||
| 503cba1315 | |||
| a313cb6244 | |||
| 7f0c6309c0 | |||
| 02c578327d | |||
| 7fec0dcff4 | |||
| 34eb7a833a | |||
| 5db3aa7ac1 | |||
| 47fd7c51a9 | |||
| 2f6f0b4e17 | |||
| 41b794f66b | |||
| e2c592367a | |||
| 7a24c56474 | |||
| 3f6bd6926a | |||
| 508af45449 | |||
| 0f89cd7875 | |||
| 29b522b8a2 | |||
| 14e63a1a54 | |||
| e559583d23 | |||
| 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
|
||||
|
||||
@@ -19,6 +19,12 @@ However, often only one value is provided.
|
||||
### Speed
|
||||
|
||||
* Garmin Speed Sensor 2
|
||||
* Has updatable firmware that requires an account for garmin.com
|
||||
* This sensor reports speed data as cadence.
|
||||
A workaround is in place.
|
||||
* This sensor sometimes counts backwards (unknown software version).
|
||||
A workaround is in place.
|
||||
|
||||
* Wahoo Speed (Model: WFRPMSPD)
|
||||
|
||||
### Cadence
|
||||
|
||||
@@ -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.3'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 3761
|
||||
versionName "v3.14.0"
|
||||
versionCode 3941
|
||||
versionName "v3.16.0"
|
||||
|
||||
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-alpha05'
|
||||
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'
|
||||
}
|
||||
|
||||
@@ -2,17 +2,7 @@
|
||||
interface TrackExporter {
|
||||
}
|
||||
class KmzTrackExporter implements TrackExporter
|
||||
KmzTrackExporter o-- FileTrackExporter
|
||||
class FileTrackExporter implements TrackExporter {
|
||||
/' One track only '/
|
||||
}
|
||||
FileTrackExporter o-- TrackWriter
|
||||
|
||||
interface TrackWriter {
|
||||
}
|
||||
class GpxTrackWriter implements TrackWriter
|
||||
class KmlTrackWriter implements TrackWriter
|
||||
|
||||
class KmlTrackExporter implements TrackExporter
|
||||
|
||||
interface TrackImporter {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -1,8 +1,32 @@
|
||||
<?xml version="1.0"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://opentracksapp.com/xmlschemas/v1">
|
||||
|
||||
<xs:element name="trackuuid"
|
||||
type="xs:string" />
|
||||
<xsd:element name="trackuuid"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
A unique track identifier stored as UUID.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
|
||||
</xs:schema>
|
||||
<xsd:element name="loss"
|
||||
type="xsd:float">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
The lost elevation in meters from the previous to the current TrackPoint.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="gain"
|
||||
type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation xml:lang="en">
|
||||
The gained elevation in meters from the previous to the current TrackPoint.
|
||||
Only used in GPX.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
@@ -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.
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.15.4: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Voice annoucements interval should be in minutes (introduced in v3.15.0)
|
||||
@@ -0,0 +1,13 @@
|
||||
v3.16.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Cycling: get distance from BLE speed sensor
|
||||
- Reworked UI presented while recording
|
||||
- UI performance optimizations
|
||||
- Exported filenames: <First 32bits UUID>_<track's name>.<extension>
|
||||
- Import multiple files via Intent (e.g., from Gadgetbridge)
|
||||
- GPX: export power data
|
||||
- Updated translations
|
||||
|
||||
Bugfix:
|
||||
- Voice announcement use minutes correctly
|
||||
|
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,14 +1,17 @@
|
||||
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.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class TestDataUtil {
|
||||
@@ -17,6 +20,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 +39,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 +110,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 +138,26 @@ 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);
|
||||
}
|
||||
|
||||
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
|
||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null);
|
||||
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
||||
while (trackPointIterator.hasNext()) {
|
||||
trackPoints.add(trackPointIterator.next());
|
||||
}
|
||||
return trackPoints;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 final List<TrackPoint> trackPointList = new ArrayList<>();
|
||||
private final 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.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 = Math.max(dataPrev.hr, stats.maxHr);
|
||||
stats.avgCadence += (dataPrev.cadence * timeElapsed);
|
||||
stats.maxCadence = Math.max(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 {
|
||||
final Instant time;
|
||||
final float hr;
|
||||
final float cadence;
|
||||
final float power;
|
||||
final 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,40 +90,42 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous);
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getCadence_rpm(), 0.01);
|
||||
assertEquals(60, current.getValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_speed() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", 1, 6184);
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 2, 8016);
|
||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184);
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016);
|
||||
|
||||
// when
|
||||
current.compute(previous, 2150);
|
||||
|
||||
// then
|
||||
assertEquals(1.20, current.getSpeed_mps(), 0.01);
|
||||
assertEquals(1.20, current.getValue().distance_m, 2150);
|
||||
assertEquals(1.20, current.getValue().speed_mps, 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_speed_rollOverCount() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
|
||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024);
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||
|
||||
// when
|
||||
current.compute(previous, 2000);
|
||||
|
||||
// then
|
||||
assertEquals(2, current.getSpeed_mps(), 0.01);
|
||||
assertEquals(1.20, current.getValue().distance_m, 2000);
|
||||
assertEquals(2, current.getValue().speed_mps, 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equals_speed_with_no_data() {
|
||||
// given
|
||||
SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress");
|
||||
SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048);
|
||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress");
|
||||
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||
|
||||
// when
|
||||
previous.toString();
|
||||
|
||||
@@ -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.content.SharedPreferences;
|
||||
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;
|
||||
@@ -17,9 +23,12 @@ import org.junit.runners.JUnit4;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.time.Instant;
|
||||
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;
|
||||
@@ -29,24 +38,40 @@ 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 SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
@@ -54,37 +79,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, 5), 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, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, 5), 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, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50, 5), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, 5), 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 = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
markers = contentProviderUtils.getMarkers(trackId);
|
||||
assertEquals(10, trackPoints.size());
|
||||
assertEquals(2, markers.size());
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -95,6 +126,7 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("Not implemented")
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_only_track() {
|
||||
@@ -108,7 +140,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
|
||||
@@ -119,7 +151,7 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -131,11 +163,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, false);
|
||||
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(false, false, false, false);
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(false, false, false);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@@ -144,7 +179,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
|
||||
@@ -155,7 +190,7 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -166,21 +201,24 @@ 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, true);
|
||||
|
||||
// 3. trackpoints
|
||||
assertTrackpoints(true, true, true, true);
|
||||
// 2. trackstatistics
|
||||
assertTrackStatistics(false, true, true);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test(expected = ImportAlreadyExistsException.class)
|
||||
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() {
|
||||
// given
|
||||
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
|
||||
PreferencesUtils.setBoolean(sharedPreferences, 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
|
||||
@@ -190,52 +228,20 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNull(importedTrack);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_only_track() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail_and_sensordata() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kmz_with_trackdetail_and_sensordata_and_pictures() {
|
||||
// TODO
|
||||
Log.e(TAG, "Test not implemented.");
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx() {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -246,7 +252,7 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -259,21 +265,30 @@ 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, true, true, true, true, true, false);
|
||||
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(true, true, false);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test(expected = ImportAlreadyExistsException.class)
|
||||
public void gpx_duplicate_trackUUID() {
|
||||
// given
|
||||
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
|
||||
PreferencesUtils.setBoolean(sharedPreferences, 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
|
||||
@@ -283,7 +298,7 @@ public class ExportImportTest {
|
||||
// 2. import
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
|
||||
importTrackId = trackImporter.importFile(inputStream);
|
||||
importTrackId = trackImporter.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
@@ -310,35 +325,111 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackpoints(boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain) {
|
||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||
private void assertTrackpoints(List<TrackPoint> trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss, boolean verifyDistance) {
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, 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);
|
||||
assertEquals("" + trackPoint, trackPoint.getHeartRate_bpm(), importedTrackPoint.getHeartRate_bpm(), 0.01);
|
||||
}
|
||||
if (verifyCadence) {
|
||||
assertEquals(trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
|
||||
assertEquals("" + trackPoint, trackPoint.getCyclingCadence_rpm(), importedTrackPoint.getCyclingCadence_rpm(), 0.01);
|
||||
}
|
||||
if (verifyPower) {
|
||||
assertEquals(trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||
assertEquals("" + trackPoint, trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||
}
|
||||
if (verifyElevationGain) {
|
||||
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
|
||||
}
|
||||
if (verifyElevationLoss) {
|
||||
assertEquals(trackPoint.getElevationLoss(), importedTrackPoint.getElevationLoss(), 0.01);
|
||||
}
|
||||
if (verifyDistance) {
|
||||
assertEquals(trackPoint.getSensorDistance(), importedTrackPoint.getSensorDistance(), 0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss, boolean verifyDistance) {
|
||||
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
|
||||
if (verifyDistance) {
|
||||
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, float speed, float altitude, float elevationGain, float heartRate, float cyclingCadence, float power, float distance) {
|
||||
TrackPoint tp = new TrackPoint(latitude, longitude, (double) altitude, Instant.ofEpochMilli(time));
|
||||
tp.setAccuracy(accuracy);
|
||||
tp.setSpeed(speed);
|
||||
tp.setHeartRate_bpm(heartRate);
|
||||
tp.setCyclingCadence_rpm(cyclingCadence);
|
||||
tp.setPower(power);
|
||||
tp.setElevationGain(elevationGain);
|
||||
tp.setSensorDistance(distance);
|
||||
return tp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
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.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.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).get(0);
|
||||
|
||||
// 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 = TestDataUtil.getTrackPoints(contentProviderUtils, 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).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).get(0);
|
||||
|
||||
// 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 = TestDataUtil.getTrackPoints(contentProviderUtils, 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());
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -25,7 +26,10 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private TrackPoint locationMock;
|
||||
private TrackPoint trackPointMock;
|
||||
|
||||
@Mock
|
||||
private TrackStatistics trackStatisticsMock;
|
||||
|
||||
@Mock
|
||||
private NotificationCompat.Builder notificationCompatBuilder;
|
||||
@@ -35,18 +39,20 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
|
||||
@Test
|
||||
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
|
||||
when(locationMock.hasAccuracy()).thenReturn(true);
|
||||
when(locationMock.getAccuracy()).thenReturn(999f);
|
||||
when(trackPointMock.hasAccuracy()).thenReturn(true);
|
||||
when(trackPointMock.getAccuracy()).thenReturn(999f);
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(0d);
|
||||
when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder);
|
||||
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
|
||||
|
||||
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||
subject.setMetricUnits(true);
|
||||
|
||||
// when
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, locationMock, 1000);
|
||||
subject.updateTrackPoint(context, locationMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 1000);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
|
||||
// then
|
||||
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
|
||||
|
||||
@@ -45,9 +45,9 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -85,6 +85,7 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
|
||||
@@ -103,7 +104,6 @@ public class TrackRecordingServiceTest {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
// Ensure that the database is empty before every test
|
||||
@@ -138,7 +138,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 +156,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 +170,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,43 +183,75 @@ 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 = TestDataUtil.getTrackPoints(contentProviderUtils, 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 = TestDataUtil.getTrackPoints(contentProviderUtils, 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();
|
||||
|
||||
// then
|
||||
assertEquals(2, contentProviderUtils.getTrackPoints(trackId).size());
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
//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);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
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.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
@@ -229,23 +261,49 @@ public class TrackRecordingServiceTest {
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, 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.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
// when
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
assertNull(service.getRecordingTrackId());
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, 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)));
|
||||
|
||||
// when
|
||||
// Just set recording track to a bogus value.
|
||||
// Make sure that the service will not start recording and will clear the bogus track.
|
||||
PreferencesUtils.setLong(context, R.string.recording_track_id_key, 123L);
|
||||
PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, 123L);
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
@@ -254,7 +312,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();
|
||||
@@ -268,13 +326,13 @@ public class TrackRecordingServiceTest {
|
||||
// then
|
||||
assertNull(newTrackId);
|
||||
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@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());
|
||||
@@ -284,27 +342,27 @@ public class TrackRecordingServiceTest {
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
assertFalse(PreferencesUtils.isRecording(context));
|
||||
assertFalse(PreferencesUtils.isRecording(sharedPreferences, context));
|
||||
assertNull(service.getRecordingTrackId());
|
||||
}
|
||||
|
||||
@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 +371,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,20 +386,19 @@ 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);
|
||||
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
|
||||
PreferencesUtils.setLong(sharedPreferences, context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
PreferencesUtils.setBoolean(sharedPreferences, context, R.string.recording_track_paused_key, !isRecording);
|
||||
}
|
||||
|
||||
// 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 +415,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);
|
||||
@@ -366,10 +423,6 @@ public class TrackRecordingServiceTest {
|
||||
location.setTime(time);
|
||||
location.setBearing(3.0f);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(ApplicationProvider.getApplicationContext());
|
||||
trackRecordingService.newTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
//TODO Needed?
|
||||
Thread.sleep(200);
|
||||
trackRecordingService.newTrackPoint(trackPoint, 50);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -22,9 +23,9 @@ import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||
@@ -34,6 +35,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 +71,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 +82,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,21 +93,30 @@ 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();
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
assertEquals(6, trackPoints.size());
|
||||
assertEquals(45.0005, trackPoints.get(5).getLatitude(), 0.01);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
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,23 +124,29 @@ 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();
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
assertEquals(2, trackPoints.size());
|
||||
assertEquals(45.000005, trackPoints.get(1).getLatitude(), 0.01);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
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
|
||||
@@ -173,11 +186,14 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
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);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
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
|
||||
@@ -199,15 +215,18 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
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);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
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,26 +249,30 @@ 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();
|
||||
|
||||
// then
|
||||
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);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
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
|
||||
@@ -270,14 +293,28 @@ public class TrackRecordingServiceTestLocation {
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = contentProviderUtils.getTrackPoints(trackId);
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, 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;
|
||||
@@ -52,6 +53,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@@ -76,7 +78,6 @@ public class TrackRecordingServiceTestLooper {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
// Let's use default values.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().apply();
|
||||
|
||||
// Ensure that the database is empty before every test
|
||||
@@ -98,28 +99,28 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRequiredAccuracy() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, 500);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, 500);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, -1);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, -1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.voice_frequency_key, 1);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.voice_frequency_key, 1);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -127,14 +128,14 @@ public class TrackRecordingServiceTestLooper {
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_maxRecordingDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.max_recording_distance_key, 50);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.max_recording_distance_key, 50);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -142,42 +143,42 @@ public class TrackRecordingServiceTestLooper {
|
||||
@Test
|
||||
public void testWithProperties_minRecordingDistanceDefault() throws TimeoutException {
|
||||
int minRecordingIntervalDefault = Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default));
|
||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, minRecordingIntervalDefault);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingDistance() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.recording_distance_interval_key, 2);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_distance_interval_key, 2);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
|
||||
PreferencesUtils.setString(context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingIntervalDefault() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, Integer.parseInt(context.getResources().getString(R.string.min_recording_interval_default)));
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testWithProperties_minRecordingInterval() throws TimeoutException {
|
||||
PreferencesUtils.setInt(context, R.string.min_recording_interval_key, 2);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.min_recording_interval_key, 2);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -185,7 +186,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
@Test
|
||||
public void testWithProperties_minRequiredAccuracyDefault() throws TimeoutException {
|
||||
int recordingGPSAccuracyDefault = Integer.parseInt(context.getResources().getString(R.string.recording_gps_accuracy_default));
|
||||
PreferencesUtils.setInt(context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.recording_gps_accuracy_key, recordingGPSAccuracyDefault);
|
||||
fullRecordingSession();
|
||||
}
|
||||
|
||||
@@ -203,12 +204,12 @@ 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);
|
||||
assertEquals(trackId, track.getId());
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
|
||||
assertEquals(trackId, PreferencesUtils.getRecordingTrackId(sharedPreferences, context));
|
||||
assertEquals(trackId, service.getRecordingTrackId());
|
||||
|
||||
// Insert a few points, markers and statistics.
|
||||
@@ -222,7 +223,7 @@ public class TrackRecordingServiceTestLooper {
|
||||
location.setTime(startTime + i * 10000);
|
||||
location.setBearing(3.0f);
|
||||
TrackPoint trackPoint = new TrackPoint(location);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
|
||||
service.newTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
if (i % 7 == 0) {
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -25,6 +26,9 @@ public class HandlerServerTest {
|
||||
@Mock
|
||||
private LocationHandler locationHandler;
|
||||
|
||||
@Mock
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private HandlerServer subject;
|
||||
|
||||
@Before
|
||||
@@ -41,16 +45,16 @@ public class HandlerServerTest {
|
||||
@Test
|
||||
public void onSharedPreferenceChanged() {
|
||||
// when
|
||||
subject.onSharedPreferenceChanged(null, null, null);
|
||||
subject.onSharedPreferenceChanged(context, sharedPreferences, null);
|
||||
|
||||
// then
|
||||
verify(locationHandler).onSharedPreferenceChanged(null, null, null);
|
||||
verify(locationHandler).onSharedPreferenceChanged(context, sharedPreferences, null);
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -25,6 +27,7 @@ import static org.mockito.Mockito.verify;
|
||||
public class LocationHandlerTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
@Mock
|
||||
private HandlerServer handlerServer;
|
||||
@@ -32,14 +35,19 @@ 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.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.recording_gps_accuracy_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, PreferencesUtils.getSharedPreferences(context), context.getString(R.string.min_recording_interval_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.recording_gps_accuracy_key));
|
||||
locationHandler.onSharedPreferenceChanged(context, sharedPreferences, context.getString(R.string.min_recording_interval_key));
|
||||
locationHandler.onStart(context);
|
||||
}
|
||||
|
||||
@@ -77,10 +85,9 @@ public class LocationHandlerTest {
|
||||
@Test
|
||||
public void testOnLocationChanged_poorAccuracy() {
|
||||
// given
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(context);
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, 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,165 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackStatisticsUpdaterTest {
|
||||
|
||||
private static final int GPS_DISTANCE = 50;
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_TestingTrack() {
|
||||
// given
|
||||
TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1));
|
||||
|
||||
// when
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
data.trackPoints.forEach(it -> subject.addTrackPoint(it, GPS_DISTANCE));
|
||||
|
||||
// then
|
||||
TrackStatistics statistics = subject.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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_not_moving() {
|
||||
// given
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_moving() {
|
||||
// given
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
tp3.setSpeed(5f);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(1.10, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_moving_and_sensor_moving() {
|
||||
// given
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
tp2.setSpeed(5f);
|
||||
TrackPoint tp3 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
tp2.setSpeed(5f);
|
||||
TrackPoint tp4 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(4000));
|
||||
tp2.setSpeed(5f);
|
||||
tp4.setSensorDistance(5f);
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
tp5.setSensorDistance(10f);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(110.57, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp4, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp5, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(125.57, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_distance_from_GPS_not_moving_and_sensor_moving() {
|
||||
// given
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000));
|
||||
TrackPoint tp4 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(4000));
|
||||
tp4.setSensorDistance(5f);
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
tp5.setSensorDistance(10f);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp2, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp3, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(tp4, GPS_DISTANCE);
|
||||
subject.addTrackPoint(tp5, GPS_DISTANCE);
|
||||
|
||||
// then
|
||||
assertEquals(15, subject.getTrackStatistics().getTotalDistance(), 0.01);
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_not_moving() {
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_moving() {
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_not_moving_and_sensor_speed() {
|
||||
}
|
||||
|
||||
@Ignore("TODO: create a concept ont to compute speed from GPS and sensor")
|
||||
@Test
|
||||
public void addTrackPoint_speed_from_GPS_moving_and_sensor_speed() {
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,9 @@ 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;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -26,59 +25,38 @@ public class AnnouncementUtilsTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void getAnnouncement() {
|
||||
public void getAnnouncement_metric() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(100);
|
||||
stats.setTotalElevationGain(6000);
|
||||
stats.setTotalElevationGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", null);
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, null);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 5 minutes 0 seconds at 240.0 kilometers per hour", announcement);
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval() {
|
||||
public void getAnnouncement_withInterval_metric() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(20000);
|
||||
stats.setTotalTime(600000);
|
||||
stats.setMovingTime(300000);
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
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;
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, "airplane", lastInterval);
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, 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("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour Lap speed of 51.2 kilometers per hour", announcement);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
assertNull(sensor.getSpeed());
|
||||
assertNull(sensor.getDistanceSpeed());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,7 +60,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
// then
|
||||
assertNull(sensor.getCadence());
|
||||
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,7 +73,7 @@ public class BluetoothUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount());
|
||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ExportUtilsTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_exists() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "kmz";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_other name name.kmz");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertTrue(exists);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_not_exists1() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "gpx";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertFalse(exists);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsExportFileExists_not_exists2() {
|
||||
// given
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String format = "kmz";
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
fileNames.add(uuid.toString().substring(0, 8) + "_name.gpx");
|
||||
fileNames.add(UUID.randomUUID().toString().substring(0, 8) + "_other name name.gpx");
|
||||
|
||||
// when
|
||||
boolean exists = ExportUtils.isExportFileExists(uuid, format, fileNames);
|
||||
|
||||
// then
|
||||
assertFalse(exists);
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,14 @@ public class PreferencesUtilsTest {
|
||||
@Test
|
||||
public void ExportTrackFileFormat_ok() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KMZ_WITH_TRACKDETAIL.name());
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL, trackFileFormat);
|
||||
@@ -36,12 +38,13 @@ public class PreferencesUtilsTest {
|
||||
@Test
|
||||
public void ExportTrackFileFormat_invalid() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
@@ -50,12 +53,13 @@ public class PreferencesUtilsTest {
|
||||
@Test
|
||||
public void ExportTrackFileFormat_noValue() {
|
||||
// given
|
||||
SharedPreferences.Editor editor = PreferencesUtils.getSharedPreferences(context).edit();
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(context);
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -25,7 +26,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,16 +43,18 @@ 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();
|
||||
|
||||
private static final SharedPreferences SHARED_PREFERENCES = PreferencesUtils.getSharedPreferences(CONTEXT);
|
||||
|
||||
/**
|
||||
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_date_local() {
|
||||
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
|
||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
|
||||
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||
}
|
||||
|
||||
@@ -60,9 +63,9 @@ public class TrackNameUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
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));
|
||||
PreferencesUtils.setString(SHARED_PREFERENCES, 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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +73,7 @@ public class TrackNameUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testTrackName_number() {
|
||||
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
|
||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
|
||||
assertEquals("Track " + TRACK_ID.getId(), 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>
|
||||
|
||||
@@ -73,11 +73,13 @@ limitations under the License.
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/gpx+xml" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/gpx" />
|
||||
</intent-filter>
|
||||
@@ -135,6 +137,7 @@ limitations under the License.
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/vnd.google-earth.kmz" />
|
||||
</intent-filter>
|
||||
@@ -192,6 +195,8 @@ limitations under the License.
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/vnd.google-earth.kml+xml" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
|
||||
public void onConfirmDeleteDone(Track.Id... trackIds) {
|
||||
boolean stopRecording = false;
|
||||
|
||||
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
|
||||
Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(PreferencesUtils.getSharedPreferences(this), this);
|
||||
for (Track.Id trackId : trackIds) {
|
||||
if (trackId.equals(recordingTrackId)) {
|
||||
stopRecording = 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;
|
||||
|
||||
/**
|
||||
@@ -68,12 +66,12 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(MarkerListActivity.this);
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, MarkerListActivity.this);
|
||||
}
|
||||
if (PreferencesUtils.isKey(MarkerListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(MarkerListActivity.this);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, MarkerListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(MarkerListActivity.this::invalidateOptionsMenu);
|
||||
@@ -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,46 +121,31 @@ 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
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -27,31 +28,24 @@ public class Startup extends Application {
|
||||
Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE);
|
||||
|
||||
//Set default values of preferences on first start.
|
||||
PreferencesUtils.resetPreferences(this, false);
|
||||
if (PreferencesUtils.getString(this, R.string.stats_units_key, "").equals("")) {
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false);
|
||||
if (PreferencesUtils.getString(sharedPreferences, this, R.string.stats_units_key, "").equals("")) {
|
||||
String statsUnits = getString(Locale.US.equals(Locale.getDefault()) ? R.string.stats_units_imperial : R.string.stats_units_metric);
|
||||
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
|
||||
PreferencesUtils.setString(sharedPreferences, this, R.string.stats_units_key, statsUnits);
|
||||
}
|
||||
|
||||
ActivityUtils.applyNightMode(this);
|
||||
ActivityUtils.applyNightMode(sharedPreferences, this);
|
||||
|
||||
//TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed.
|
||||
if (PreferencesUtils.isRecording(this)) {
|
||||
if (PreferencesUtils.isRecording(sharedPreferences, this)) {
|
||||
Log.e(TAG, "Reset recordingTrackId; likely the TrackRecordingService crashed.");
|
||||
PreferencesUtils.setLong(this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
PreferencesUtils.setLong(sharedPreferences, this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||
}
|
||||
|
||||
//In debug builds: show thread and VM warnings.
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Enabling strict mode");
|
||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.enableDefaults();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -29,6 +30,7 @@ import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -37,12 +39,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;
|
||||
@@ -104,18 +109,18 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
|
||||
metricUnits = PreferencesUtils.isMetricUnits(TrackListActivity.this);
|
||||
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this);
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_id_key, key)) {
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(TrackListActivity.this);
|
||||
recordingTrackId = PreferencesUtils.getRecordingTrackId(sharedPreferences, TrackListActivity.this);
|
||||
if (key != null && PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
trackRecordingServiceConnection.startAndBind(TrackListActivity.this);
|
||||
}
|
||||
}
|
||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackListActivity.this);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackListActivity.this);
|
||||
}
|
||||
if (key != null) {
|
||||
runOnUiThread(() -> {
|
||||
@@ -192,17 +197,20 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
|
||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||
viewBinding.trackList.setOnItemClickListener((parent, view, position, trackId) -> {
|
||||
Intent newIntent;
|
||||
if (trackId == recordingTrackId.getId()) {
|
||||
// Is recording -> open record activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||
startActivity(newIntent);
|
||||
} else {
|
||||
// Not recording -> open detail activity.
|
||||
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
|
||||
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
|
||||
this,
|
||||
new Pair<>(view.findViewById(R.id.list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
|
||||
startActivity(newIntent, activityOptions.toBundle());
|
||||
}
|
||||
startActivity(newIntent);
|
||||
});
|
||||
|
||||
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
|
||||
@@ -223,7 +231,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 +240,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,12 +251,18 @@ 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();
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
}
|
||||
|
||||
@@ -320,43 +334,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 +483,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,9 +22,9 @@ 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.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
@@ -56,6 +56,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
|
||||
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
||||
|
||||
public static final String VIEW_TRACK_ICON = "track_icon";
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
@@ -85,6 +87,8 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
if (savedInstanceState != null) {
|
||||
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||
}
|
||||
|
||||
postponeEnterTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,7 +143,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.findItem(R.id.track_detail_markers).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(this));
|
||||
menu.findItem(R.id.track_detail_resume_track).setVisible(!PreferencesUtils.isRecording(PreferencesUtils.getSharedPreferences(this), this));
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
setTitle(track != null ? track.getName() : "");
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
@@ -147,43 +151,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
|
||||
@@ -268,4 +280,9 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startPostponedEnterTransitionWith(View viewIcon, View viewName) {
|
||||
ViewCompat.setTransitionName(viewIcon, TrackRecordedActivity.VIEW_TRACK_ICON);
|
||||
startPostponedEnterTransition();
|
||||
}
|
||||
}
|
||||
@@ -97,9 +97,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_track_paused_key, key)) {
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(TrackRecordingActivity.this);
|
||||
recordingTrackPaused = PreferencesUtils.isRecordingTrackPaused(sharedPreferences, TrackRecordingActivity.this);
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
@@ -170,7 +170,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setLockscreenPolicy() {
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this);
|
||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, TrackRecordingActivity.this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
setShowWhenLocked(showOnLockScreen);
|
||||
@@ -182,7 +182,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setScreenOnPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this);
|
||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(sharedPreferences, TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
@@ -192,7 +192,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void setFullscreenPolicy() {
|
||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(TrackRecordingActivity.this);
|
||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(sharedPreferences, TrackRecordingActivity.this);
|
||||
|
||||
if (keepScreenOn) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
@@ -206,7 +206,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
super.onStart();
|
||||
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
trackRecordingServiceConnection.startConnection(this);
|
||||
trackDataHub.start();
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -46,49 +47,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 +93,70 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO Check preference handling.
|
||||
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
String category = aggregatedStatistic.getCategory();
|
||||
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
|
||||
metricsUnits = PreferencesUtils.isMetricUnits(sharedPreferences, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||