Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5594a125d0 | |||
| 13380afef0 | |||
| 9c4c8131f5 | |||
| 7f10ae5c3f | |||
| 1fa45a3393 | |||
| f1b0c83196 | |||
| f0f7bca54b | |||
| 8f39bf139c | |||
| 85585969bd | |||
| 976e7be370 | |||
| 48dc64d4d7 | |||
| 16e69e850b | |||
| 5ece301ac0 | |||
| a5e500cdb1 | |||
| 8433ed82fe | |||
| 7c7defa233 | |||
| 1f84e44a8d | |||
| 858a8f20a8 | |||
| 6761c41dbf | |||
| 59e55be214 | |||
| 1b68cba1ba | |||
| 32f47340a9 | |||
| e25814c674 | |||
| 522baa5193 | |||
| b597b56b84 | |||
| d956e8d3f0 | |||
| 274781ccfa | |||
| e7e3ef23ff | |||
| 3289fa27e4 | |||
| 96fc074a4b | |||
| eaab09747c | |||
| bdb2a9dbc6 | |||
| 7e26571124 | |||
| 33b23a6e8f | |||
| 584adc7c57 | |||
| 667da76c4f | |||
| df6a3a7f04 | |||
| b254f5e40b | |||
| 7a9c3efb3b | |||
| 53dd7ada1f | |||
| 6a5edcbdef | |||
| 6e954261bb | |||
| 277061ad27 | |||
| c03f9fc792 | |||
| 657b82b57a | |||
| 4608edec56 | |||
| b086c30696 | |||
| 7eca033a8e | |||
| 6f3efd9ff2 | |||
| 7a2741e508 | |||
| 017eee8b68 | |||
| 024d7f8652 | |||
| 05c3a89682 | |||
| a3872581bb | |||
| e75451dab3 | |||
| 2897171132 | |||
| ed2f506b7b | |||
| 1e2ba3798d | |||
| a0a8ef9016 | |||
| e1f4406674 | |||
| d9b7f77bbb | |||
| e60dcbe539 | |||
| fc0cec6157 | |||
| 0ac34e0638 | |||
| 242147e491 | |||
| eba7a7fd89 | |||
| 348f74c4b3 | |||
| d898d51f1a | |||
| 6e9c95f232 | |||
| 9c7f458802 | |||
| f52b153e3a | |||
| 145cbf870b | |||
| 8e52574378 | |||
| e3ca1b7f21 | |||
| 2cdae4f460 | |||
| 422e171406 | |||
| 130581da6f | |||
| 9cd212f6b1 | |||
| 08701dcbf9 | |||
| 0f7f332087 | |||
| 9eed5aa70f | |||
| ea8cd9b6fc | |||
| 0ef40e7fab | |||
| 919ecbbffb | |||
| 21369860a7 | |||
| 7bad6a106a | |||
| bdf0c91714 | |||
| 453cbd705e | |||
| 0669eadc01 | |||
| 7a661cb521 | |||
| bb7cba0e47 | |||
| 1402563702 | |||
| 4d8c8e4ec9 | |||
| ef7f36d49e | |||
| e2ebf04621 | |||
| 029b60efff | |||
| 70bfa3e862 | |||
| 9f1087c9e6 | |||
| c64bae827b | |||
| 05bde1cf33 | |||
| 13c65b25ae | |||
| 0ab0f5d7a3 | |||
| 70ca0d50e2 | |||
| 8da64ad28d | |||
| 48d19aaa67 | |||
| 63bb512e3a | |||
| 3205c9de8a | |||
| bda1603e87 | |||
| 28d2f42096 | |||
| d83bc0b32a | |||
| 87508fca3f | |||
| 3250be9480 | |||
| 3848dd8fbd | |||
| 022cf5835b | |||
| 325681c6cd | |||
| 54be1b74fb | |||
| fd1a53d1a6 | |||
| 19f6a477c4 | |||
| 94babc9cd2 | |||
| 26414b0c7c | |||
| 956031264f | |||
| 7d8ac1f411 | |||
| a4121319fa | |||
| 1fdf1dd607 | |||
| 7759d1f9b0 | |||
| 43b114b64b | |||
| a808c52d48 | |||
| bb90c53057 | |||
| bd5bf530f0 | |||
| a4580fc84e | |||
| 9ab34eb846 | |||
| 997ddbb4f1 | |||
| 86a0054c24 | |||
| 3bd00c906a | |||
| 684419e86e | |||
| d42d1214ea | |||
| db24639829 | |||
| 0f6480197b | |||
| cce62c4c47 | |||
| bfdc04fbd9 | |||
| 18702bb6b3 | |||
| 65e920e8b6 | |||
| 7d24262b38 | |||
| 223ad70d36 | |||
| 7f618da7a6 | |||
| 0332b9eb87 | |||
| 29aefff788 | |||
| c9478ea38d | |||
| 1255ec8111 | |||
| f9d3108cb2 | |||
| e18d6b942d | |||
| cc5ced6383 | |||
| 163879487c | |||
| d7dd9f7c1b | |||
| 1259c42d28 | |||
| 9bccbe79a4 | |||
| 401e07d7e7 |
@@ -1,31 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 6 * * 1'
|
||||
|
||||
jobs:
|
||||
analyse:
|
||||
name: Analyse
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: java
|
||||
|
||||
- run: |
|
||||
./gradlew build
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Linting
|
||||
|
||||
on: [ push ]
|
||||
|
||||
jobs:
|
||||
analyse:
|
||||
name: Analyse
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
|
||||
- run: ./gradlew lint
|
||||
@@ -1,19 +1,25 @@
|
||||
#https://github.com/marketplace/actions/android-emulator-runner
|
||||
name: Test
|
||||
on: [push, pull_request]
|
||||
on: [ push ]
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 45
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
api-level: [ 23, 26, 29 ]
|
||||
api-level: [ 23, 26 ] #TODO Re-enable 29
|
||||
target: [ default ]
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2.19.0
|
||||
uses: reactivecircus/android-emulator-runner@v2.19.1
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
target: ${{ matrix.target }}
|
||||
@@ -21,3 +27,11 @@ jobs:
|
||||
profile: Nexus 6
|
||||
script: ./gradlew connectedCheck
|
||||
|
||||
- name: Archive
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: code-coverage-report
|
||||
path: build/reports/androidTests/
|
||||
retention-days: 7
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -51,16 +51,16 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
||||
|
||||
## Screenshots
|
||||
<div>
|
||||
<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">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot3.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot4.png">
|
||||
</div>
|
||||
<div>
|
||||
<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">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot5.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot6.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot7.png">
|
||||
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot8.png">
|
||||
</div>
|
||||
|
||||
## Features
|
||||
@@ -79,6 +79,8 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
||||
* cycling: speed and distance
|
||||
* cycling: cadence
|
||||
* cycling: power meter
|
||||
* running: speed and cadence
|
||||
An overview of tested sensors: [README_TESTED_SENSORS.md](README_TESTED_SENSORS.md)
|
||||
|
||||
### Gadgetbridge integration
|
||||
|
||||
|
||||
@@ -28,9 +28,10 @@ Requirement: `gem install fastlane`
|
||||
(requires keystore)
|
||||
2. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
|
||||
3. Update store meta data (e.g., description and screenshots)
|
||||
`fastlane supply --version_code VERSION_CODE --skip_upload_apk`
|
||||
(use the version code from the manifest)
|
||||
(requires API key)
|
||||
`fastlane metadata`
|
||||
(requires API key configured in `Appfile`)
|
||||
|
||||
Supported languages: https://support.google.com/googleplay/android-developer/table/4419860
|
||||
|
||||
## Translations (localization)
|
||||
|
||||
|
||||
@@ -50,3 +50,8 @@ Also the distance is not computed.
|
||||
* Wahoo Kickr v4.0
|
||||
* Rotor 2INpower DM Road (only supports power measurement, cadence measurement is propietary @ firmware v1.061)
|
||||
|
||||
## RSC: running cadence and speed
|
||||
|
||||
We do not support type of movement (i.e., walking vs running).
|
||||
|
||||
* Polar Stride
|
||||
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
||||
classpath 'com.android.tools.build:gradle:7.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,8 @@ def getVersionName = { ->
|
||||
commandLine 'git', 'describe', '--tags'
|
||||
standardOutput = stdout
|
||||
}
|
||||
exec {
|
||||
commandLine 'git', 'log', '--format="%h"', '-n 1'
|
||||
standardOutput = stdout
|
||||
}
|
||||
return stdout.toString().replace('\n', '-').replace('"', '').trim()
|
||||
}
|
||||
catch (ignored) {
|
||||
return stdout.toString().trim()
|
||||
} catch (ignored) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -39,11 +34,11 @@ android {
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 31
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
@@ -55,17 +50,17 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 4253
|
||||
versionName "v3.18.6"
|
||||
versionCode 4410
|
||||
versionName "v3.20.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
|
||||
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
targetSdkVersion 31
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -92,7 +87,7 @@ android {
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
@@ -103,5 +98,7 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.10.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:3.12.0'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.0'
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ class TrackPoint {
|
||||
- sensor_power
|
||||
}
|
||||
|
||||
|
||||
class Marker {
|
||||
- id
|
||||
- trackId
|
||||
@@ -58,6 +57,26 @@ class Marker {
|
||||
- photoUrl
|
||||
}
|
||||
|
||||
class DataField {
|
||||
- key
|
||||
- title
|
||||
- isVisible
|
||||
- isPrimary
|
||||
- isWide
|
||||
}
|
||||
|
||||
class StatisticData {
|
||||
- value
|
||||
- unit
|
||||
- description
|
||||
}
|
||||
StatisticData o-- DataField
|
||||
|
||||
class Layout {
|
||||
- profile
|
||||
}
|
||||
Layout "1" o-- "many" DataField
|
||||
|
||||
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).
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
README.md
|
||||
Appfile
|
||||
report.xml
|
||||
@@ -16,23 +16,11 @@
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Runs all the tests"
|
||||
lane :test do
|
||||
gradle(task: "test")
|
||||
end
|
||||
|
||||
desc "Submit a new Beta Build to Crashlytics Beta"
|
||||
lane :beta do
|
||||
gradle(task: "clean assembleRelease")
|
||||
crashlytics
|
||||
|
||||
# sh "your_script.sh"
|
||||
# You can also use other beta testing services here
|
||||
end
|
||||
|
||||
desc "Deploy a new version to the Google Play"
|
||||
lane :deploy do
|
||||
gradle(task: "clean assembleRelease")
|
||||
upload_to_play_store
|
||||
desc "Upload metadata"
|
||||
lane :metadata do
|
||||
upload_to_play_store(
|
||||
skip_upload_apk: true,
|
||||
skip_upload_changelogs: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,4 +2,3 @@ v3.13.2: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Crash in landscape mode
|
||||
|
||||
|
||||
@@ -15,4 +15,3 @@ Developer:
|
||||
- Replaced communication via SharedPreferences
|
||||
- Use non-static service binder
|
||||
- Use altitude in the code base (instead of elevation)
|
||||
|
||||
|
||||
@@ -8,4 +8,3 @@ Bugfix:
|
||||
- Crash if using pace for voice announcements (introduced in v3.17.0)
|
||||
- Statistics showed pace initially (introduced in v3.17.0)
|
||||
- Rare crash when deleting tracks
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.19.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Support for Running Speed and Cadence (RCS) sensors implemented
|
||||
- When sharing a track: a share picture is created
|
||||
- No vibration when starting a recording
|
||||
- While recording: UI shows most recently received altitude, speed, and bearing (even if not stored)
|
||||
|
||||
Bugfix:
|
||||
- Computation of total duration should not include paused durations (requires re-import)
|
||||
- (again) Use custom summaries if present
|
||||
|
||||
Developer:
|
||||
- Upgrade to SDK31 and AGP7.0.0 (requires JDK11)
|
||||
- TrackRecordingService: extacted sensor fusion (TrackPointCreator) and TrackPoint storage (TrackRecordingManager)
|
||||
- ExportImportTest is now deterministics/ constant
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.19.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Updated translations.
|
||||
|
||||
Bugfix:
|
||||
- Export All Tracks bug fixed.
|
||||
- Resume Track bug fixed.
|
||||
@@ -0,0 +1,9 @@
|
||||
v3.20.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- While recording: statistics UI is now configurable
|
||||
|
||||
Bugfix:
|
||||
- Changing track name/ category while recording was ignored (introduced in v3.19.0)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 574 KiB After Width: | Height: | Size: 574 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 456 KiB |
|
Before Width: | Height: | Size: 630 KiB After Width: | Height: | Size: 630 KiB |
|
Before Width: | Height: | Size: 416 KiB After Width: | Height: | Size: 416 KiB |
|
Before Width: | Height: | Size: 508 KiB After Width: | Height: | Size: 508 KiB |
|
Before Width: | Height: | Size: 798 KiB After Width: | Height: | Size: 798 KiB |
@@ -1,11 +1,11 @@
|
||||
OpenTracks estas sporto spuranta aplikon kiu tute respektas vian privatecon.
|
||||
OpenTracks estas aplikaĵo por registri vian sportan aktivecon, kiu plene respektas vian privatecon.
|
||||
|
||||
Karakterizaĵoj:
|
||||
* Sporto spurado: spuri via sporto kaj subĉielaj aktivecoj
|
||||
* Fotoj kaj signoj: mark interesaj lokoj dum spurado
|
||||
* Altitudo akiro kaj perdo: tra barometric sentilo
|
||||
* Altitudo montrita en EGM2008 (supre signifas marnivelo); eksportita kiel WGS84
|
||||
* # Vo? o anoncoj
|
||||
* Elportas Bluetooth LE sentiloj: koro imposto, rapido kaj distanco (biciklado), kadenco (biciklado), kaj potenco metro (biciklado)
|
||||
* Eksportado datumoj: eksporti vojetoj aŭ kiel KMZ (incl. fotoj), KML, aŭ GPX
|
||||
* Neniu aliro al Interreto: Internet ne estas uzita
|
||||
Eblaĵoj:
|
||||
• registri vian sportan kaj alian aktivecon eksterdome
|
||||
• fotoj kaj markoj: marki interesajn lokojn dum registri aktivecon
|
||||
• registri ŝanĝon de altitudo uzante barometron
|
||||
• montri altitudon en EGM2008 (super averaĝa marnivelo); elporti ilin kiel WGS84
|
||||
• voĉaj anoncoj
|
||||
• subteno por sentiloj BLE por registri vian pulson, rapidon, veturigitan distancon, pedal-rapidon, povumon (biciklado)
|
||||
• elporti datumojn al dosieroj KMZ (kun fotoj), KML aŭ GPX
|
||||
• funkcias malkonekte: interreta permeso ne estas uzata
|
||||
|
||||
@@ -1 +1 @@
|
||||
Sporto spuristo ke tute respektas vian privatecon.
|
||||
Registrilo de via sporta aktiveco, kiu respektas vian privatecon.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.18.6: „OpenTracks“
|
||||
|
||||
Pakeitimai:
|
||||
- „Rodyti žemėlapyje“ funkcionalumas per KML, o ne KMZ
|
||||
- Pranešime balsu nėra programos pavadinimo
|
||||
- Įrašant: aukščio padidėjimas / praradimas atnaujinamas kas sekundę
|
||||
- Išversti parduotuvės aprašymai
|
||||
|
||||
Bufiksai:
|
||||
- Ištaisyti neteisingi balso pranešimų vertimai
|
||||
|
||||
Programuotojas:
|
||||
- „Android12“ leidimai (BLUETOOTH_SCAN)
|
||||
- „Android12“ leidimai (ACCESS_COARSE_LOCATION)
|
||||
- Tvirtinimas „TrackPoints“ (testavimas)
|
||||
- Pašalinti nepanaudoti vertimai
|
||||
@@ -0,0 +1,11 @@
|
||||
„OpenTracks“ yra sporto stebėjimo programa, visiškai gerbianti jūsų privatumą.
|
||||
|
||||
Funkcijos:
|
||||
* Sporto sekimas: stebėkite savo sportinę ir lauko veiklą
|
||||
* Nuotraukos ir žymekliai: stebėdami stebėkite įdomias vietas
|
||||
* Aukščio padidėjimas ir praradimas: per barometrinį jutiklį
|
||||
* EGM2008 nurodytas aukštis (virš vidutinio jūros lygio); eksportuota kaip WGS84
|
||||
* Balso pranešimai
|
||||
* Palaiko „Bluetooth LE“ jutiklius: širdies ritmą, greitį ir atstumą (važiuojant dviračiu), ritmą (dviračiu) ir galios matuoklį (važiuojant dviračiu)
|
||||
* Eksportuoti duomenis: eksportuoti takelius kaip KMZ (įskaitant nuotraukas), KML arba GPX
|
||||
* Nėra interneto prieigos: internetas nenaudojamas
|
||||
@@ -0,0 +1 @@
|
||||
Sporto sekimo priemonė, visiškai gerbianti jūsų privatumą.
|
||||
@@ -0,0 +1 @@
|
||||
OpenTracks
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.18.6: OpenTracks
|
||||
|
||||
Endringer:
|
||||
- «Vis på kart»-funksjonalitet via KML istedenfor KMZ
|
||||
- Stemmekunngjøringer inkluderer ikke programnavn
|
||||
- Under opptak: Høydeendringer registrert hvert sekund
|
||||
- Butikk-oversettelser oversatt
|
||||
|
||||
Feilfikser:
|
||||
- Fikset ugyldige stemmekunngjøringsoversettelser
|
||||
|
||||
Utviklere:
|
||||
- Android12-tilganger (Blåtannsskanning)
|
||||
- Android12-tilganger (tilgang til grovposisjon)
|
||||
- Fastsetting for TrackPoints (testing)
|
||||
- Fjernet ubrukte oversettelser
|
||||
@@ -0,0 +1,11 @@
|
||||
OpenTracks er en sportsporingsapp som respekterer ditt personvern.
|
||||
|
||||
Funksjoner:
|
||||
* Sportsporing: Registrer dine sports- og utendørsaktiviteter
|
||||
* Bilder og markører: Marker interessante steder under sporing
|
||||
* Total stigning og nedstigning: via barometersensor
|
||||
* Høyde vist i EGM2008 (over havnivå); eksportert som WGS84
|
||||
* Talemeldinger
|
||||
* Støtter Blåtann LE-sensorer: puls, hastighet og distanse (sykling), kadens (sykling) og wattmåler (sykling)
|
||||
* Eksport av data: Enten som KMZ (inkl. bilder), KML eller GPX
|
||||
* Ingen internettilgang: Internett brukes ikke
|
||||
@@ -0,0 +1 @@
|
||||
En sportsregistrerer som respekterer ditt personvern.
|
||||
@@ -0,0 +1 @@
|
||||
OpenTracks
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.18.6: OpenTracks
|
||||
|
||||
Mudanças:
|
||||
- Funcionalidade "Mostrar no mapa" via KML em vez de KMZ
|
||||
- O anúncio de voz não inclui o nome do aplicativo
|
||||
- Durante a gravação: o ganho / perda de elevação é atualizado a cada segundo
|
||||
- Descrições da loja traduzidas
|
||||
|
||||
Correções:
|
||||
- Correções de traduções inválidas no anúncios de voz
|
||||
|
||||
Desenvolvedores:
|
||||
- Permissões Android12 (BLUETOOTH_SCAN)
|
||||
- Permissões Android12 (ACCESS_COARSE_LOCATION)
|
||||
- Confirmação de TrackPoints (em teste)
|
||||
- Removidas as traduções não utilizadas
|
||||
@@ -7,5 +7,5 @@ Características:
|
||||
* Altitude mostrada no EGM2008 (acima do nível médio do mar); exportado como WGS84
|
||||
* Anúncios de voz
|
||||
* Suporta sensores Bluetooth LE: frequência cardíaca, velocidade e distância (ciclismo), cadência (ciclismo) e medidor de potência (ciclismo)
|
||||
* Exportar dados: exporte trilhas como KMZ (incluindo fotos), KML ou GPX
|
||||
* Exportar dados: exporte trilhas como KMZ (incluindo fotos), KML, ou GPX
|
||||
* Sem acesso à Internet: a Internet não é usada
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.18.6: OpenTracks
|
||||
|
||||
Spremembe:
|
||||
|
||||
- "Prikaži na zemljevidu"-funkcionalnost prek KML namesto KMZ
|
||||
- Glasovno obvestilo ne vključuje imena aplikacije
|
||||
- Med snemanjem: pridobitev/izguba nadmorske višine se posodobi vsako sekundo
|
||||
- Prevedeni opisi trgovin
|
||||
|
||||
Bufiksi:
|
||||
- Vključene so bile naslednje možnosti: popravljeni neveljavni prevodi glasovnih napovedi
|
||||
|
||||
Razvijalec:
|
||||
- Android12 (BLUETOOTH_SCAN)
|
||||
- Android12 dovoljenja (ACCESS_COARSE_LOCATION)
|
||||
- Zagotavljanje za TrackPoints (testiranje)
|
||||
@@ -1,7 +1,7 @@
|
||||
OpenTracks je aplikacija za sledenje športu, ki popolnoma spoštuje vašo zasebnost.
|
||||
|
||||
Značilnosti:
|
||||
* Športno sledenje: sledite svojim športnim aktivnostim in aktivnostim na prostem.
|
||||
* Sledenje športu: sledite svojim športnim aktivnostim in aktivnostim na prostem.
|
||||
* Fotografije in oznake: med sledenjem označite zanimive lokacije
|
||||
* Pridobivanje in izgubljanje nadmorske višine: prek barometričnega senzorja
|
||||
* Nadmorska višina je prikazana v EGM2008 (nad srednjo morsko gladino); izvozi se kot WGS84
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.18.6: OpenTracks
|
||||
|
||||
Förändring:
|
||||
- "Visa på kartan" - funktionalitet via KML istället för KMZ
|
||||
- Röstmeddelande inkluderar inte appnamn
|
||||
- Under inspelning: höjd vinst / förlust uppdateras varje sekund
|
||||
- Store beskrivningar översatt
|
||||
|
||||
Bufixar:
|
||||
- Fast ogiltig röst tillkännagivande översättningar
|
||||
|
||||
Utvecklare:
|
||||
- Android12 behörigheter (BLUETOOTH_SCAN)
|
||||
- Android12 behörigheter (ACCESS_COARSE_LOCATION)
|
||||
- Bedömning av spårpunkter (provning)
|
||||
- Bort oanvända översättningar
|
||||
@@ -1,5 +1,6 @@
|
||||
#Wed Jul 28 23:10:13 CEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.test.espresso.DataInteraction;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onData;
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
* Espresso test that checks the unit for activity category selected.
|
||||
* Also it checks that unit is properly when user change from a pace activity to an speed one.
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoCategoryUnitTest {
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public TestRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
@Rule
|
||||
public TestRule mLocaleRule = new PreferenceMetricUnitRule(context, true);
|
||||
|
||||
@Test
|
||||
public void changeBetweenCategoryUpdateProperlyUnitTest() {
|
||||
// Start record.
|
||||
ViewInteraction appCompatImageButton = onView(
|
||||
allOf(withId(R.id.controller_record), withContentDescription("Record"),
|
||||
childAtPosition(
|
||||
allOf(withId(R.id.controller_container),
|
||||
childAtPosition(
|
||||
withId(R.id.controller_fragment),
|
||||
0)),
|
||||
0),
|
||||
isDisplayed()));
|
||||
appCompatImageButton.perform(click());
|
||||
|
||||
{
|
||||
// Change to running activity (pace activity).
|
||||
ViewInteraction appCompatImageView = onView(
|
||||
allOf(withId(R.id.stats_activity_type_icon),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.ScrollView")),
|
||||
0),
|
||||
7)));
|
||||
appCompatImageView.perform(scrollTo(), click());
|
||||
|
||||
DataInteraction imageView = onData(anything())
|
||||
.inAdapterView(allOf(withId(R.id.choose_activity_type_grid_view),
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.LinearLayout")),
|
||||
0)))
|
||||
.atPosition(1);
|
||||
imageView.perform(click());
|
||||
}
|
||||
|
||||
// Pace (min/km) in all fields.
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_speed_unit)));
|
||||
textView.perform(waitFor(2000));
|
||||
textView.check(matches(withText("min/km")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_average_speed_unit)));
|
||||
textView.check(matches(withText("min/km")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_max_speed_unit)));
|
||||
textView.check(matches(withText("min/km")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_moving_speed_unit)));
|
||||
textView.check(matches(withText("min/km")));
|
||||
}
|
||||
|
||||
{
|
||||
// Change to biking activity (speed activity).
|
||||
ViewInteraction appCompatImageView = onView(
|
||||
allOf(withId(R.id.stats_activity_type_icon),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.ScrollView")),
|
||||
0),
|
||||
7)));
|
||||
appCompatImageView.perform(scrollTo(), click());
|
||||
|
||||
DataInteraction imageView = onData(anything())
|
||||
.inAdapterView(allOf(withId(R.id.choose_activity_type_grid_view),
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.LinearLayout")),
|
||||
0)))
|
||||
.atPosition(9);
|
||||
imageView.perform(click());
|
||||
}
|
||||
|
||||
// Speed (km/h) in all fields.
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_speed_unit)));
|
||||
textView.perform(waitFor(2000));
|
||||
textView.check(matches(withText("km/h")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_average_speed_unit)));
|
||||
textView.check(matches(withText("km/h")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_max_speed_unit)));
|
||||
textView.check(matches(withText("km/h")));
|
||||
}
|
||||
{
|
||||
ViewInteraction textView = onView(allOf(withId(R.id.stats_moving_speed_unit)));
|
||||
textView.check(matches(withText("km/h")));
|
||||
}
|
||||
}
|
||||
|
||||
private static Matcher<View> childAtPosition(
|
||||
final Matcher<View> parentMatcher, final int position) {
|
||||
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Child at position " + position + " in parent ");
|
||||
parentMatcher.describeTo(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesSafely(View view) {
|
||||
ViewParent parent = view.getParent();
|
||||
return parent instanceof ViewGroup && parentMatcher.matches(parent)
|
||||
&& view.equals(((ViewGroup) parent).getChildAt(position));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.espresso.NoMatchingViewException;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.ViewAssertion;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.action.MotionEvents;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.*;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.*;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoCustomLayoutTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<TrackListActivity> mActivityTestRule = new ActivityTestRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule =
|
||||
GrantPermissionRule.grant(
|
||||
"android.permission.ACCESS_FINE_LOCATION");
|
||||
|
||||
@Test
|
||||
public void customLayoutTest() {
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
|
||||
// Get custom layout preferences and check all data fields are showed.
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
|
||||
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
|
||||
|
||||
// stop recording
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
trackControllerStopButton.perform(veryLongTouch(1600));
|
||||
}
|
||||
|
||||
private static class RecyclerViewItemCountAssertion implements ViewAssertion {
|
||||
private final int expectedCount;
|
||||
|
||||
public RecyclerViewItemCountAssertion(int expectedCount) {
|
||||
this.expectedCount = expectedCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(View view, NoMatchingViewException noViewFoundException) {
|
||||
if (noViewFoundException != null) {
|
||||
throw noViewFoundException;
|
||||
}
|
||||
|
||||
RecyclerView recyclerView = (RecyclerView) view;
|
||||
RecyclerView.Adapter adapter = recyclerView.getAdapter();
|
||||
assertThat(adapter.getItemCount(), is(expectedCount));
|
||||
}
|
||||
}
|
||||
|
||||
private static Matcher<View> childAtPosition(
|
||||
final Matcher<View> parentMatcher, final int position) {
|
||||
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Child at position " + position + " in parent ");
|
||||
parentMatcher.describeTo(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesSafely(View view) {
|
||||
ViewParent parent = view.getParent();
|
||||
return parent instanceof ViewGroup && parentMatcher.matches(parent)
|
||||
&& view.equals(((ViewGroup) parent).getChildAt(position));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.action.ViewActions.replaceText;
|
||||
import static androidx.test.espresso.action.ViewActions.scrollTo;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.test.espresso.PerformException;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.action.MotionEvents;
|
||||
import androidx.test.espresso.util.HumanReadables;
|
||||
import androidx.test.espresso.util.TreeIterables;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.hamcrest.core.IsInstanceOf;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoEditTrackRecordingTest {
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<TrackListActivity> mActivityTestRule = new ActivityTestRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule =
|
||||
GrantPermissionRule.grant(
|
||||
"android.permission.ACCESS_FINE_LOCATION",
|
||||
"android.permission.ACCESS_COARSE_LOCATION");
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void espressoEditTrackRecordingTest() {
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
trackControllerRecordButton.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
|
||||
|
||||
// wait; stay recording
|
||||
trackControllerRecordButton.perform(waitFor(15000));
|
||||
|
||||
// open menu
|
||||
ViewInteraction overflowMenuButton = onView(
|
||||
allOf(withContentDescription("More options"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(R.id.toolbar),
|
||||
2),
|
||||
2),
|
||||
isDisplayed()));
|
||||
overflowMenuButton.perform(click());
|
||||
|
||||
// click on edit
|
||||
ViewInteraction appCompatTextView = onView(
|
||||
allOf(withId(R.id.title), withText("Edit"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(R.id.content),
|
||||
0),
|
||||
0),
|
||||
isDisplayed()));
|
||||
appCompatTextView.perform(click());
|
||||
|
||||
// change name for "New Name"
|
||||
ViewInteraction textInputEditText = onView(
|
||||
allOf(withId(R.id.track_edit_name),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
|
||||
0),
|
||||
1)));
|
||||
textInputEditText.perform(scrollTo(), replaceText("New Name"));
|
||||
|
||||
ViewInteraction textInputEditText2 = onView(
|
||||
allOf(withId(R.id.track_edit_name), withText("New Name"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
|
||||
0),
|
||||
1),
|
||||
isDisplayed()));
|
||||
textInputEditText2.perform(closeSoftKeyboard());
|
||||
|
||||
// save edition
|
||||
ViewInteraction appCompatButton = onView(
|
||||
allOf(withId(R.id.track_edit_save),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withClassName(is("android.widget.LinearLayout")),
|
||||
3),
|
||||
1),
|
||||
isDisplayed()));
|
||||
appCompatButton.perform(click());
|
||||
|
||||
//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));
|
||||
|
||||
// check track's name is "New Name"
|
||||
ViewInteraction textView = onView(
|
||||
allOf(withId(R.id.stats_name_value),
|
||||
withParent(withParent(IsInstanceOf.<View>instanceOf(android.widget.ScrollView.class))),
|
||||
isDisplayed()));
|
||||
textView.check(matches(withText("New Name")));
|
||||
}
|
||||
}
|
||||
|
||||
private static Matcher<View> childAtPosition(
|
||||
final Matcher<View> parentMatcher, final int position) {
|
||||
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Child at position " + position + " in parent ");
|
||||
parentMatcher.describeTo(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesSafely(View view) {
|
||||
ViewParent parent = view.getParent();
|
||||
return parent instanceof ViewGroup && parentMatcher.matches(parent)
|
||||
&& view.equals(((ViewGroup) parent).getChildAt(position));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ViewAction waitId(final int viewId, final long millis) {
|
||||
return new ViewAction() {
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "wait for a specific view with id <" + viewId + "> during " + millis + " millis.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(final UiController uiController, final View view) {
|
||||
uiController.loopMainThreadUntilIdle();
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final long endTime = startTime + millis;
|
||||
final Matcher<View> viewMatcher = withId(viewId);
|
||||
|
||||
do {
|
||||
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
|
||||
// found view with required ID
|
||||
if (viewMatcher.matches(child)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uiController.loopMainThreadForAtLeast(50);
|
||||
}
|
||||
while (System.currentTimeMillis() < endTime);
|
||||
|
||||
// timeout happens
|
||||
throw new PerformException.Builder()
|
||||
.withActionDescription(this.getDescription())
|
||||
.withViewDescription(HumanReadables.describe(view))
|
||||
.withCause(new TimeoutException())
|
||||
.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -112,12 +112,11 @@ public class TestDataUtil {
|
||||
}
|
||||
|
||||
public static TrackPoint createTrackPoint(int i) {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(i + 1));
|
||||
trackPoint.setLatitude(INITIAL_LATITUDE + (double) i / 10000.0);
|
||||
trackPoint.setLongitude(INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||
trackPoint.setAccuracy((float) i / 100.0f);
|
||||
trackPoint.setHorizontalAccuracy(Distance.of(i / 100.0f));
|
||||
trackPoint.setAltitude(i * ALTITUDE_INTERVAL);
|
||||
trackPoint.setTime(Instant.ofEpochSecond(i + 1));
|
||||
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
|
||||
|
||||
trackPoint.setHeartRate_bpm(100f + i);
|
||||
@@ -160,13 +159,14 @@ public class TestDataUtil {
|
||||
}
|
||||
|
||||
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
|
||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null);
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||
ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
||||
while (trackPointIterator.hasNext()) {
|
||||
trackPoints.add(trackPointIterator.next());
|
||||
}
|
||||
return trackPoints;
|
||||
}
|
||||
}
|
||||
|
||||
public static Pair<Track.Id, TrackStatistics> buildTrackWithTrackPoints(ContentProviderUtils contentProviderUtils, int numberOfPoints) {
|
||||
Track dummyTrack = new Track();
|
||||
|
||||
@@ -12,16 +12,15 @@ public class TestSensorDataUtil {
|
||||
|
||||
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);
|
||||
TrackPoint tp = new TrackPoint(type, time);
|
||||
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.setHorizontalAccuracy(Distance.of(1f));
|
||||
tp.setAltitude(1f);
|
||||
tp.setTime(time);
|
||||
tp.setSpeed(Speed.of(5f + (i / 10f)));
|
||||
tp.setAltitudeGain(3f);
|
||||
tp.setAltitudeLoss(3f);
|
||||
|
||||
@@ -13,27 +13,25 @@ public class TrackPointTest {
|
||||
|
||||
@Test
|
||||
public void isRecent_true() {
|
||||
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL);
|
||||
tp.setTime(Instant.now());
|
||||
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, 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));
|
||||
TrackPoint tp = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.now().minus(2, ChronoUnit.MINUTES));
|
||||
|
||||
assertFalse(tp.isRecent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void distanceToPrevious() {
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0))
|
||||
.setLatitude(0)
|
||||
.setLongitude(0.0001);
|
||||
|
||||
TrackPoint tp2 = new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
TrackPoint tp2 = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(1))
|
||||
.setLatitude(0)
|
||||
.setLongitude(0.0002);
|
||||
|
||||
|
||||
@@ -135,10 +135,10 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
List<TrackPoint> trackPoints = new ArrayList<>(numPoints);
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(i))
|
||||
.setLatitude(37.0 + (double) i / 10000.0)
|
||||
.setLongitude(57.0 - (double) i / 10000.0)
|
||||
.setAccuracy((float) i / 100.0f)
|
||||
.setHorizontalAccuracy(Distance.of(i / 100.0f))
|
||||
.setAltitude(i * 2.5);
|
||||
trackPoints.add(trackPoint);
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous, Distance.ofMM(2150));
|
||||
|
||||
// then
|
||||
assertEquals(2.15, current.getValue().distance.toM(), 0.01);
|
||||
assertEquals(1.20, current.getValue().speed.toMPS(), 0.01);
|
||||
assertEquals(2.15, current.getValue().getDistance().toM(), 0.01);
|
||||
assertEquals(1.20, current.getValue().getSpeed().toMPS(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,8 +118,8 @@ public class SensorDataCyclingTest {
|
||||
current.compute(previous, Distance.ofMM(2000));
|
||||
|
||||
// then
|
||||
assertEquals(2, current.getValue().distance.toM(), 0.01);
|
||||
assertEquals(2, current.getValue().speed.toMPS(), 0.01);
|
||||
assertEquals(2, current.getValue().getDistance().toM(), 0.01);
|
||||
assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -18,6 +18,7 @@ package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
@@ -32,7 +33,7 @@ public class TrackStubUtils {
|
||||
public static final long INITIAL_TIME = 1000L;
|
||||
private static final double INITIAL_LATITUDE = 22;
|
||||
private static final double INITIAL_LONGITUDE = 22;
|
||||
private static final float INITIAL_ACCURACY = 5;
|
||||
private static final Distance INITIAL_ACCURACY = Distance.of(5);
|
||||
private static final Speed INITIAL_SPEED = Speed.of(10);
|
||||
private static final float INITIAL_BEARING = 3.0f;
|
||||
|
||||
@@ -55,7 +56,7 @@ public class TrackStubUtils {
|
||||
.setLatitude(latitude)
|
||||
.setLongitude(longitude)
|
||||
.setAltitude(altitude)
|
||||
.setAccuracy(INITIAL_ACCURACY)
|
||||
.setHorizontalAccuracy(INITIAL_ACCURACY)
|
||||
.setSpeed(INITIAL_SPEED)
|
||||
.setBearing(INITIAL_BEARING);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -19,11 +25,15 @@ import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -31,7 +41,6 @@ import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Altitude;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
@@ -39,25 +48,24 @@ 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.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
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.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
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>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExportImportTest {
|
||||
|
||||
private static final String TAG = ExportImportTest.class.getSimpleName();
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@@ -81,7 +89,6 @@ public class ExportImportTest {
|
||||
private File tmpFile;
|
||||
private Uri tmpFileUri;
|
||||
|
||||
private Track track;
|
||||
private List<Marker> markers = new ArrayList<>();
|
||||
private List<TrackPoint> trackPoints = new ArrayList<>();
|
||||
|
||||
@@ -99,35 +106,50 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
@After
|
||||
public void FileTearDown() {
|
||||
public void FileTearDown() throws TimeoutException {
|
||||
tmpFile.deleteOnExit();
|
||||
tmpFileUri = null;
|
||||
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
service.getHandlerServer().setClock(Clock.systemUTC());
|
||||
}
|
||||
|
||||
public void setUp(boolean hasSensorDistance) throws TimeoutException {
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
|
||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||
trackId = service.startNewTrack();
|
||||
|
||||
Distance sensorDistance = hasSensorDistance ? Distance.of(5) : null;
|
||||
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance), 0);
|
||||
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance);
|
||||
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, sensorDistance), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
|
||||
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:04Z"), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
|
||||
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:05Z"), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
|
||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:06Z"), ZoneId.of("CET")));
|
||||
handlerServer.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:20Z"), ZoneId.of("CET")));
|
||||
service.resumeCurrentTrack();
|
||||
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
|
||||
service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance), 0);
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:21Z"), ZoneId.of("CET")));
|
||||
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:21Z"), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
|
||||
|
||||
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:22Z"), 3, 16, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
|
||||
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:23Z"), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:24Z"), ZoneId.of("CET")));
|
||||
handlerServer.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
|
||||
service.endCurrentTrack();
|
||||
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
track.setIcon(TRACK_ICON);
|
||||
track.setCategory(TRACK_CATEGORY);
|
||||
track.setDescription(TRACK_DESCRIPTION);
|
||||
@@ -142,53 +164,14 @@ public class ExportImportTest {
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (trackId != null) {
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
}
|
||||
if (importTrackId != null) {
|
||||
contentProviderUtils.deleteTrack(context, importTrackId);
|
||||
}
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void kml_with_trackdetail_and_sensordata() throws TimeoutException, IOException {
|
||||
setUp(true);
|
||||
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
|
||||
importTrackId = importer.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
|
||||
// 2. trackpoints
|
||||
TrackPointAssert a = new TrackPointAssert()
|
||||
.noAccuracy();
|
||||
a.assertEquals(trackPoints, TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId));
|
||||
|
||||
// 2. trackstatistics
|
||||
assertTrackStatistics(false, true);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
}
|
||||
|
||||
//TODO Does not test images
|
||||
@LargeTest
|
||||
@Test
|
||||
@@ -223,8 +206,30 @@ public class ExportImportTest {
|
||||
.noAccuracy();
|
||||
a.assertEquals(trackPoints, TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId));
|
||||
|
||||
// 2. trackstatistics
|
||||
assertTrackStatistics(false, true);
|
||||
// 3. trackstatistics
|
||||
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
|
||||
|
||||
// Time
|
||||
assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.getStartTime());
|
||||
assertEquals(Instant.parse("2020-02-02T02:02:24Z"), importedTrackStatistics.getStopTime());
|
||||
|
||||
assertEquals(track.getTrackStatistics().getTotalTime(), importedTrackStatistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(8), importedTrackStatistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
|
||||
|
||||
// Distance
|
||||
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
|
||||
|
||||
// Speed
|
||||
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
|
||||
assertEquals(Speed.of(3.75), importedTrackStatistics.getAverageSpeed());
|
||||
assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageMovingSpeed());
|
||||
|
||||
// Altitude
|
||||
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
|
||||
assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01);
|
||||
assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
|
||||
assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
@@ -278,14 +283,14 @@ public class ExportImportTest {
|
||||
|
||||
// then
|
||||
// 1. track
|
||||
Track trackImported = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(trackImported);
|
||||
assertEquals(track.getCategory(), trackImported.getCategory());
|
||||
assertEquals(track.getDescription(), trackImported.getDescription());
|
||||
assertEquals(track.getName(), trackImported.getName());
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals(track.getCategory(), importedTrack.getCategory());
|
||||
assertEquals(track.getDescription(), importedTrack.getDescription());
|
||||
assertEquals(track.getName(), importedTrack.getName());
|
||||
|
||||
//TODO exporting and importing a track icon is not yet supported by GpxTrackWriter.
|
||||
//assertEquals(track.getIcon(), trackImported.getIcon());
|
||||
//assertEquals(track.getIcon(), importedTrack.getIcon());
|
||||
|
||||
// 2. trackpoints
|
||||
// The GPX exporter does not support exporting TrackPoints without lat/lng.
|
||||
@@ -300,7 +305,29 @@ public class ExportImportTest {
|
||||
a.assertEquals(trackPointsWithCoordinates, TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId));
|
||||
|
||||
// 3. trackstatistics
|
||||
assertTrackStatistics(true, false);
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
TrackStatistics importedTrackStatistics = importedTrack.getTrackStatistics();
|
||||
|
||||
// Time
|
||||
assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.getStartTime());
|
||||
assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime());
|
||||
|
||||
assertEquals(Duration.ofSeconds(20), importedTrackStatistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(19), importedTrackStatistics.getMovingTime());
|
||||
|
||||
// Distance
|
||||
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
|
||||
|
||||
// Speed
|
||||
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
|
||||
assertEquals(Speed.of(1.5), importedTrackStatistics.getAverageSpeed());
|
||||
assertEquals(Speed.of(1.5789473684210527), importedTrackStatistics.getAverageMovingSpeed());
|
||||
|
||||
// Altitude
|
||||
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
|
||||
assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01);
|
||||
assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
|
||||
assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
|
||||
|
||||
// 4. markers
|
||||
assertMarkers();
|
||||
@@ -353,56 +380,49 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrackStatistics(boolean isGpx, boolean verifyDistance) {
|
||||
double delta = isGpx ? 0.1 : 0.01;
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
private void sendLocation(TrackPointCreator handlerServer, Instant time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) {
|
||||
Location location = new Location("mock");
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
location.setAccuracy(accuracy);
|
||||
location.setSpeed(speed);
|
||||
location.setAltitude(altitude);
|
||||
|
||||
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());
|
||||
handlerServer.setAltitudeSumManager(new AltitudeSumManager() {
|
||||
@Override
|
||||
public void fill(@NonNull TrackPoint trackPoint) {
|
||||
trackPoint.setAltitudeGain(altitudeGain);
|
||||
trackPoint.setAltitudeLoss(altitudeGain);
|
||||
}
|
||||
});
|
||||
|
||||
// Speed
|
||||
assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed());
|
||||
assertEquals(trackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed());
|
||||
assertEquals(trackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed());
|
||||
}
|
||||
handlerServer.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
|
||||
@Override
|
||||
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
sensorDataSet.set(new SensorDataCyclingPower("power", "power", power));
|
||||
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", heartRate));
|
||||
|
||||
// Altitude
|
||||
assertEquals(trackStatistics.getMinAltitude(), importedTrackStatistics.getMinAltitude(), delta);
|
||||
if (isGpx) {
|
||||
assertEquals(trackStatistics.getMaxAltitude(), importedTrackStatistics.getMaxAltitude(), 2);
|
||||
} else {
|
||||
assertEquals(trackStatistics.getMaxAltitude(), importedTrackStatistics.getMaxAltitude(), delta);
|
||||
}
|
||||
assertEquals(trackStatistics.getTotalAltitudeGain(), importedTrackStatistics.getTotalAltitudeGain(), delta);
|
||||
assertEquals(trackStatistics.getTotalAltitudeLoss(), importedTrackStatistics.getTotalAltitudeLoss(), delta);
|
||||
}
|
||||
SensorDataCycling.Cadence cadence = Mockito.mock(SensorDataCycling.Cadence.class);
|
||||
Mockito.when(cadence.hasValue()).thenReturn(true);
|
||||
Mockito.when(cadence.getValue()).thenReturn(cyclingCadence);
|
||||
sensorDataSet.set(cadence);
|
||||
|
||||
private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, float heartRate, float cyclingCadence, float power, Distance distance) {
|
||||
TrackPoint tp = new TrackPoint(latitude, longitude, Altitude.WGS84.of(altitude), Instant.ofEpochMilli(time));
|
||||
tp.setAccuracy(accuracy);
|
||||
tp.setSpeed(Speed.of(speed));
|
||||
tp.setHeartRate_bpm(heartRate);
|
||||
tp.setCyclingCadence_rpm(cyclingCadence);
|
||||
tp.setPower(power);
|
||||
tp.setAltitudeGain(altitudeGain);
|
||||
tp.setAltitudeLoss(altitudeGain); //TODO
|
||||
tp.setSensorDistance(distance);
|
||||
return tp;
|
||||
SensorDataCycling.DistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCycling.DistanceSpeed.Data.class);
|
||||
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
|
||||
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
|
||||
SensorDataCycling.DistanceSpeed distanceSpeed = Mockito.mock(SensorDataCycling.DistanceSpeed.class);
|
||||
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
|
||||
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
|
||||
|
||||
sensorDataSet.set(distanceSpeed);
|
||||
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return sensorDataSet;
|
||||
}
|
||||
});
|
||||
|
||||
handlerServer.setClock(Clock.fixed(time, ZoneId.of("CET")));
|
||||
handlerServer.getLocationHandler().onLocationChanged(location);
|
||||
}
|
||||
}
|
||||
@@ -50,12 +50,12 @@ public class TrackPointAssert {
|
||||
}
|
||||
|
||||
if (assertAccuracy) {
|
||||
Assert.assertEquals(expected.hasAccuracy(), actual.hasAccuracy());
|
||||
if (expected.hasAccuracy()) {
|
||||
Assert.assertEquals(expected.getAccuracy(), actual.getAccuracy(), delta);
|
||||
Assert.assertEquals(expected.hasHorizontalAccuracy(), actual.hasHorizontalAccuracy());
|
||||
if (expected.hasHorizontalAccuracy()) {
|
||||
Assert.assertEquals(expected.getHorizontalAccuracy().toM(), actual.getHorizontalAccuracy().toM(), delta);
|
||||
}
|
||||
} else {
|
||||
Assert.assertFalse(actual.hasAccuracy());
|
||||
Assert.assertFalse(actual.hasHorizontalAccuracy());
|
||||
}
|
||||
|
||||
Assert.assertEquals(expected.hasSensorDistance(), actual.hasSensorDistance());
|
||||
@@ -80,6 +80,19 @@ public class TrackPointAssert {
|
||||
}
|
||||
|
||||
public void assertEquals(List<TrackPoint> expected, List<TrackPoint> actual) {
|
||||
try {
|
||||
Assert.assertEquals(expected.size(), actual.size());
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Expected: " + expected + " actual: " + actual);
|
||||
}
|
||||
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
try {
|
||||
assertEquals(expected.get(i), actual.get(i));
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Expected: " + expected.get(i) + " actual: " + actual.get(i), e);
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(expected.size(), actual.size());
|
||||
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
|
||||
@@ -40,20 +40,21 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
|
||||
@Test
|
||||
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
|
||||
when(trackPointMock.hasAccuracy()).thenReturn(true);
|
||||
when(trackPointMock.getAccuracy()).thenReturn(999f);
|
||||
when(trackPointMock.hasHorizontalAccuracy()).thenReturn(true);
|
||||
when(trackPointMock.getHorizontalAccuracy()).thenReturn(Distance.of(999f));
|
||||
when(trackPointMock.getSpeed()).thenReturn(Speed.of(0));
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
||||
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder);
|
||||
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean()))
|
||||
.thenReturn(notificationCompatBuilder);
|
||||
|
||||
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||
subject.setMetricUnits(true);
|
||||
|
||||
// when
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 1000);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, 100);
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, Distance.of(100));
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, Distance.of(100));
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, Distance.of(1000));
|
||||
subject.updateTrackPoint(context, trackStatisticsMock, trackPointMock, Distance.of(100));
|
||||
|
||||
// then
|
||||
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -22,6 +29,7 @@ import android.content.SharedPreferences;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
@@ -37,13 +45,16 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
@@ -51,16 +62,12 @@ 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.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for the track recording service.
|
||||
*
|
||||
@@ -99,6 +106,14 @@ public class TrackRecordingServiceTest {
|
||||
return new Intent(context, TrackRecordingService.class);
|
||||
}
|
||||
|
||||
private final AltitudeSumManager altitudeSumManager = new AltitudeSumManager() {
|
||||
@Override
|
||||
public void fill(@NonNull TrackPoint trackPoint) {
|
||||
trackPoint.setAltitudeGain(0f);
|
||||
trackPoint.setAltitudeLoss(0f);
|
||||
}
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Set up the mock content resolver
|
||||
@@ -123,6 +138,7 @@ public class TrackRecordingServiceTest {
|
||||
if (service.isRecording() || service.isPaused()) {
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
service.getHandlerServer().setClock(Clock.systemUTC());
|
||||
|
||||
// Ensure that the database is empty after every test
|
||||
contentProviderUtils.deleteAllTracks(context);
|
||||
@@ -189,40 +205,34 @@ public class TrackRecordingServiceTest {
|
||||
assertTrue(service.isRecording());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_start() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
|
||||
// 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
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
|
||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
handlerServer.stopGPS();
|
||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
||||
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());
|
||||
TrackPointAssert a = new TrackPointAssert();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:03Z"))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -231,52 +241,79 @@ public class TrackRecordingServiceTest {
|
||||
// given
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
|
||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
handlerServer.stopGPS();
|
||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
// then
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
//when
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
|
||||
service.resumeTrack(trackId);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
|
||||
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());
|
||||
TrackPointAssert a = new TrackPointAssert();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:03Z"))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:04Z"))
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_resumeStoppedTrack() throws TimeoutException, InterruptedException {
|
||||
public void testRecording_resumeStoppedTrack() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
|
||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
handlerServer.stopGPS();
|
||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
||||
service.endCurrentTrack();
|
||||
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
// when
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
|
||||
service.resumeTrack(trackId);
|
||||
newTrackPoint(service);
|
||||
handlerServer.stopGPS();
|
||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
handlerServer.onNewTrackPoint(new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z")), Distance.of(50));
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(4, 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());
|
||||
assertEquals(TrackPoint.Type.TRACKPOINT, trackPoints.get(3).getType());
|
||||
TrackPointAssert a = new TrackPointAssert();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:03Z"))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:04Z")),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z"))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -285,22 +322,32 @@ public class TrackRecordingServiceTest {
|
||||
// given
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
|
||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
handlerServer.stopGPS();
|
||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
||||
service.pauseCurrentTrack();
|
||||
|
||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||
|
||||
// when
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
|
||||
service.endCurrentTrack();
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
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());
|
||||
TrackPointAssert a = new TrackPointAssert();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:02:02Z")),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:03Z"))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -353,13 +400,23 @@ public class TrackRecordingServiceTest {
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testInsertWaypointMarker_validWaypoint() throws TimeoutException, InterruptedException {
|
||||
public void testInsertWaypointMarker_validWaypoint() throws TimeoutException {
|
||||
// given
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||
.getService();
|
||||
|
||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
||||
handlerServer.stopGPS();
|
||||
|
||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
assertTrue(service.isRecording());
|
||||
newTrackPoint(service);
|
||||
handlerServer.onNewTrackPoint(
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z"))
|
||||
.setLatitude(10)
|
||||
.setLongitude(10)
|
||||
, Distance.of(50));
|
||||
|
||||
// when
|
||||
Marker.Id markerId = service.insertMarker(null, null, null, null);
|
||||
@@ -395,11 +452,7 @@ public class TrackRecordingServiceTest {
|
||||
addTrack(dummyTrack);
|
||||
}
|
||||
|
||||
private static void newTrackPoint(TrackRecordingService trackRecordingService) throws InterruptedException {
|
||||
newTrackPoint(trackRecordingService, 45.0f, 35f, 5, 10, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed) throws InterruptedException {
|
||||
static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed) {
|
||||
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@@ -410,9 +463,10 @@ public class TrackRecordingServiceTest {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(time))
|
||||
.setLongitude(longitude)
|
||||
.setLatitude(latitude)
|
||||
.setAccuracy(accuracy)
|
||||
.setHorizontalAccuracy(Distance.of(accuracy))
|
||||
.setSpeed(Speed.of(speed))
|
||||
.setBearing(3.0f);
|
||||
trackRecordingService.newTrackPoint(trackPoint, 50);
|
||||
|
||||
trackRecordingService.getHandlerServer().onNewTrackPoint(trackPoint, Distance.of(50));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -95,6 +96,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
|
||||
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
|
||||
.getService();
|
||||
service.getHandlerServer().stopGPS();
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -108,7 +110,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
public void testOnLocationChangedAsync_movingAccurate() throws Exception {
|
||||
// given
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
service.setAltitudeSumManager(altitudeSumManager);
|
||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||
@@ -127,50 +129,50 @@ public class TrackRecordingServiceTestLocation {
|
||||
TrackPointAssert a = new TrackPointAssert()
|
||||
.ignoreTime();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(1)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.0001)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(2)
|
||||
.setHorizontalAccuracy(Distance.of(2))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.0001)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(3)
|
||||
.setHorizontalAccuracy(Distance.of(3))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.0002)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(4)
|
||||
.setHorizontalAccuracy(Distance.of(4))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.0003)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(5)
|
||||
.setHorizontalAccuracy(Distance.of(5))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.0004)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(6)
|
||||
.setHorizontalAccuracy(Distance.of(6))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), trackPoints);
|
||||
@@ -181,7 +183,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
|
||||
// given
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
service.setAltitudeSumManager(altitudeSumManager);
|
||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||
@@ -200,22 +202,22 @@ public class TrackRecordingServiceTestLocation {
|
||||
TrackPointAssert a = new TrackPointAssert()
|
||||
.ignoreTime();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(1)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.0005)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(6)
|
||||
.setHorizontalAccuracy(Distance.of(6))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), trackPoints);
|
||||
@@ -226,7 +228,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
public void testOnLocationChangedAsync_idle() throws Exception {
|
||||
// given
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
service.setAltitudeSumManager(altitudeSumManager);
|
||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||
@@ -245,29 +247,29 @@ public class TrackRecordingServiceTestLocation {
|
||||
TrackPointAssert a = new TrackPointAssert()
|
||||
.ignoreTime();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(1)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT) //TODO Why is this added? Systems is idle and not moving at all.
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Why is this added? Systems is idle and not moving at all.
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(2)
|
||||
.setHorizontalAccuracy(Distance.of(2))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(6)
|
||||
.setHorizontalAccuracy(Distance.of(6))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), trackPoints);
|
||||
@@ -278,7 +280,8 @@ public class TrackRecordingServiceTestLocation {
|
||||
public void testOnLocationChangedAsync_idle_withMovement() throws Exception {
|
||||
// given
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
service.setAltitudeSumManager(altitudeSumManager);
|
||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
||||
service.getHandlerServer().stopGPS();
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||
@@ -297,36 +300,36 @@ public class TrackRecordingServiceTestLocation {
|
||||
TrackPointAssert a = new TrackPointAssert()
|
||||
.ignoreTime();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(1)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT) //TODO Check why this trackPoint is inserted.
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(2)
|
||||
.setHorizontalAccuracy(Distance.of(2))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT) //TODO Check why this trackPoint is inserted.
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(5)
|
||||
.setHorizontalAccuracy(Distance.of(5))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(6)
|
||||
.setHorizontalAccuracy(Distance.of(6))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), trackPoints);
|
||||
@@ -337,8 +340,8 @@ public class TrackRecordingServiceTestLocation {
|
||||
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
|
||||
// given
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
service.setAltitudeSumManager(altitudeSumManager);
|
||||
service.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
|
||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
||||
service.getHandlerServer().setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
@@ -346,10 +349,11 @@ public class TrackRecordingServiceTestLocation {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(@NonNull TrackPoint trackPoint) {
|
||||
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
sensorDataSet.set(new SensorDataHeartRate("sensorName", "sensorAddress", 5f));
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return sensorDataSet;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -370,56 +374,56 @@ public class TrackRecordingServiceTestLocation {
|
||||
TrackPointAssert a = new TrackPointAssert()
|
||||
.ignoreTime();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(1)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(2)
|
||||
.setHorizontalAccuracy(Distance.of(2))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(3)
|
||||
.setHorizontalAccuracy(Distance.of(3))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(4)
|
||||
.setHorizontalAccuracy(Distance.of(4))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(5)
|
||||
.setHorizontalAccuracy(Distance.of(5))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(6)
|
||||
.setHorizontalAccuracy(Distance.of(6))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f)
|
||||
@@ -431,7 +435,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
public void testOnLocationChangedAsync_segment() throws Exception {
|
||||
// given
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
service.setAltitudeSumManager(altitudeSumManager);
|
||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||
@@ -450,45 +454,45 @@ public class TrackRecordingServiceTestLocation {
|
||||
TrackPointAssert a = new TrackPointAssert()
|
||||
.ignoreTime();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(1)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null)
|
||||
.setLatitude(45.1)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(2)
|
||||
.setHorizontalAccuracy(Distance.of(2))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.1)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(3)
|
||||
.setHorizontalAccuracy(Distance.of(3))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null)
|
||||
.setLatitude(45.2)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(4)
|
||||
.setHorizontalAccuracy(Distance.of(4))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.2)
|
||||
.setLongitude(35)
|
||||
.setAccuracy(5)
|
||||
.setHorizontalAccuracy(Distance.of(5))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL)
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), trackPoints);
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -234,15 +235,16 @@ public class TrackRecordingServiceTestLooper {
|
||||
|
||||
// Insert a few points, markers and statistics.
|
||||
for (int i = 0; i < 30; i++) {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT)
|
||||
//TODO Should send locations to LocationHandler instead of TrackPoints to HandlerServer?
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, service.getHandlerServer().createNow())
|
||||
.setLongitude(35.0f + i / 10.0f)
|
||||
.setLatitude(45.0f - i / 5.0f)
|
||||
.setAccuracy(5)
|
||||
.setHorizontalAccuracy(Distance.of(5))
|
||||
.setSpeed(Speed.of(10))
|
||||
.setBearing(3.0f);
|
||||
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
|
||||
service.newTrackPoint(trackPoint, prefAccuracy);
|
||||
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
|
||||
service.getHandlerServer().onNewTrackPoint(trackPoint, prefAccuracy);
|
||||
|
||||
if (i % 7 == 0) {
|
||||
service.insertMarker(null, null, null, null);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
@@ -13,16 +17,15 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LocationHandlerTest {
|
||||
|
||||
@@ -30,7 +33,7 @@ public class LocationHandlerTest {
|
||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
|
||||
@Mock
|
||||
private HandlerServer handlerServer;
|
||||
private TrackPointCreator handlerServer;
|
||||
|
||||
@InjectMocks
|
||||
private LocationHandler locationHandler;
|
||||
@@ -46,9 +49,13 @@ public class LocationHandlerTest {
|
||||
// Let's use default values.
|
||||
sharedPreferences.edit().clear().commit();
|
||||
|
||||
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);
|
||||
Mockito.when(handlerServer.createNow())
|
||||
.thenReturn(Instant.now());
|
||||
|
||||
//TODO REMOVE
|
||||
// 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, sharedPreferences);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +67,7 @@ public class LocationHandlerTest {
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(handlerServer, times(1)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
verify(handlerServer, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +83,7 @@ public class LocationHandlerTest {
|
||||
locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(handlerServer, times(0)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
verify(handlerServer, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,14 +92,14 @@ public class LocationHandlerTest {
|
||||
@Test
|
||||
public void testOnLocationChanged_poorAccuracy() {
|
||||
// given
|
||||
int prefAccuracy = PreferencesUtils.getRecordingGPSAccuracy(sharedPreferences, context);
|
||||
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
|
||||
|
||||
// when
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, prefAccuracy + 1, 5, System.currentTimeMillis()));
|
||||
locationHandler.onLocationChanged(createLocation(45f, 35f, (float) (prefAccuracy.toM() + 1), 5, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
// no newTrackPoint called
|
||||
verify(handlerServer, times(0)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
verify(handlerServer, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -105,7 +112,7 @@ public class LocationHandlerTest {
|
||||
locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||
|
||||
// then
|
||||
verify(handlerServer, times(1)).sendTrackPoint(any(TrackPoint.class), any(Integer.class));
|
||||
verify(handlerServer, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
package de.dennisguse.opentracks.services.handlers;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class HandlerServerTest {
|
||||
public class TrackPointCreatorTest {
|
||||
|
||||
@Mock
|
||||
private Context context;
|
||||
|
||||
@Mock
|
||||
private HandlerServer.HandlerServerInterface server;
|
||||
private TrackPointCreator.Callback server;
|
||||
|
||||
@Mock
|
||||
private LocationHandler locationHandler;
|
||||
@@ -29,39 +31,31 @@ public class HandlerServerTest {
|
||||
@Mock
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private HandlerServer subject;
|
||||
private TrackPointCreator subject;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
subject = new HandlerServer(locationHandler, server);
|
||||
subject = new TrackPointCreator(locationHandler, server);
|
||||
subject.start(context);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
subject.stop(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onSharedPreferenceChanged() {
|
||||
// when
|
||||
subject.onSharedPreferenceChanged(context, sharedPreferences, null);
|
||||
|
||||
// then
|
||||
verify(locationHandler).onSharedPreferenceChanged(context, sharedPreferences, null);
|
||||
subject.stop();
|
||||
}
|
||||
|
||||
@Ignore("ServiceExecutor disabled for #822")
|
||||
@Test
|
||||
public void sendTrackPoint() throws InterruptedException {
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||
int accuracy = 50;
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, null);
|
||||
Distance horizontalAccuracyThreshold = Distance.of(50);
|
||||
|
||||
// when
|
||||
subject.sendTrackPoint(trackPoint, accuracy);
|
||||
subject.onNewTrackPoint(trackPoint, horizontalAccuracyThreshold);
|
||||
|
||||
// then
|
||||
Thread.sleep(10); // Wait for executor service
|
||||
verify(server).newTrackPoint(trackPoint, accuracy);
|
||||
verify(server).newTrackPoint(trackPoint, horizontalAccuracyThreshold);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
|
||||
|
||||
public class BluetoothUtilsTest {
|
||||
|
||||
@@ -87,4 +90,18 @@ public class BluetoothUtilsTest {
|
||||
// then
|
||||
assertEquals(40, power_w);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseRunningSpeedAndCadence_with_distance() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, 0, 0);
|
||||
characteristic.setValue(new byte[]{2, 0, 5, 80, 12, 1});
|
||||
|
||||
// when
|
||||
SensorDataRunning sensor = BluetoothUtils.parseRunningSpeedAndCadence("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(Speed.of(5), sensor.getSpeed());
|
||||
assertEquals(80, sensor.getCadence(), 0.01);
|
||||
assertEquals(Distance.of(26.8), sensor.getTotalDistance());
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,14 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesUtilsTest {
|
||||
@@ -64,4 +69,131 @@ public class PreferencesUtilsTest {
|
||||
// then
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_default() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertTrue(layout.getFields().size() > 0);
|
||||
assertEquals(layout.getProfile(), context.getString(R.string.default_activity_default));
|
||||
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_1() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(
|
||||
context.getString(R.string.stats_custom_layout_fields_key),
|
||||
"run;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
|
||||
editor.apply();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getProfile(), "run");
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
assertTrue(layout.getFields().get(0).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(layout.getFields().get(1).isVisible());
|
||||
assertFalse(layout.getFields().get(1).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
|
||||
assertFalse(layout.getFields().get(2).isVisible());
|
||||
assertTrue(layout.getFields().get(2).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(layout.getFields().get(3).isVisible());
|
||||
assertFalse(layout.getFields().get(3).isPrimary());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_coordinatesIsWide() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(
|
||||
context.getString(R.string.stats_custom_layout_fields_key),
|
||||
"walking;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
|
||||
editor.apply();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getProfile(), "walking");
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
assertTrue(layout.getFields().get(0).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(layout.getFields().get(1).isVisible());
|
||||
assertFalse(layout.getFields().get(1).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
|
||||
assertFalse(layout.getFields().get(2).isVisible());
|
||||
assertTrue(layout.getFields().get(2).isPrimary());
|
||||
assertTrue(layout.getFields().get(2).isWide());
|
||||
|
||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(layout.getFields().get(3).isVisible());
|
||||
assertFalse(layout.getFields().get(3).isPrimary());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCustomLayout() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
Layout layoutSrc = new Layout("road cycling");
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), false, true, false));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
|
||||
|
||||
// when
|
||||
PreferencesUtils.setCustomLayout(sharedPreferences, context, layoutSrc);
|
||||
|
||||
// then
|
||||
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layout_fields_key), null);
|
||||
assertNotNull(csv);
|
||||
assertEquals(csv,
|
||||
"road cycling;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
|
||||
|
||||
Layout layoutDst = PreferencesUtils.getCustomLayout(sharedPreferences, context);
|
||||
assertEquals(layoutSrc.getProfile(), layoutDst.getProfile());
|
||||
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
|
||||
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
|
||||
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
|
||||
assertEquals(layoutSrc.getFields().get(i).isVisible(), layoutDst.getFields().get(i).isVisible());
|
||||
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class StatisticsUtilsTest extends TestCase {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void testEmptyValue() {
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_distance_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_total_time_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_speed_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_moving_time_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_speed_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_max_speed_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_moving_speed_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_altitude_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_gain_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_loss_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_pace_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_moving_pace_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_pace_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_fastest_pace_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_coordinates), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_coordinates_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_heart_rate_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_cadence_key)));
|
||||
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_power_key)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilterVisible() {
|
||||
// given
|
||||
Layout layout = new Layout("profile");
|
||||
layout.addField(new DataField("key1", "title1", true, true, false));
|
||||
layout.addField(new DataField("key2", "title2", false, true, false));
|
||||
layout.addField(new DataField("key3", "title3", true, true, false));
|
||||
layout.addField(new DataField("key4", "title4", false, true, false));
|
||||
layout.addField(new DataField("key5", "title5", true, true, false));
|
||||
|
||||
// when
|
||||
Layout resultTrue = StatisticsUtils.filterVisible(layout, true);
|
||||
Layout resultFalse = StatisticsUtils.filterVisible(layout, false);
|
||||
|
||||
// then
|
||||
assertEquals(resultTrue.getFields().size(), 3);
|
||||
assertTrue(resultTrue.getFields().stream().anyMatch(f -> f.getKey().equals("key1")));
|
||||
assertTrue(resultTrue.getFields().stream().anyMatch(f -> f.getKey().equals("key3")));
|
||||
assertTrue(resultTrue.getFields().stream().anyMatch(f -> f.getKey().equals("key5")));
|
||||
|
||||
assertEquals(resultFalse.getFields().size(), 2);
|
||||
assertTrue(resultFalse.getFields().stream().anyMatch(f -> f.getKey().equals("key2")));
|
||||
assertTrue(resultFalse.getFields().stream().anyMatch(f -> f.getKey().equals("key4")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StatisticDataBuilderTest extends TestCase {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private Track trackMock;
|
||||
|
||||
@Mock
|
||||
private TrackStatistics trackStatisticsMock;
|
||||
|
||||
@Mock
|
||||
private TrackRecordingService.RecordingData recordingDataMock;
|
||||
|
||||
@Mock
|
||||
private SensorStatistics sensorStatisticsMock;
|
||||
|
||||
@Test
|
||||
public void testFromRecordingData() {
|
||||
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
||||
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
|
||||
// given
|
||||
Layout layout = new Layout(context.getString(R.string.default_activity_default));
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), false, true, false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, true);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 3);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromRecordingData_allFields() {
|
||||
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
||||
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
|
||||
// given
|
||||
Layout layout = new Layout(context.getString(R.string.default_activity_default));
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_speed_key), context.getString(R.string.stats_average_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_pace_key), context.getString(R.string.stats_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_pace_key), context.getString(R.string.stats_average_moving_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_pace_key), context.getString(R.string.stats_average_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_fastest_pace_key), context.getString(R.string.stats_fastest_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_altitude_key), context.getString(R.string.stats_altitude), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_gain_key), context.getString(R.string.stats_gain), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_loss_key), context.getString(R.string.stats_loss), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_coordinates_key), context.getString(R.string.stats_coordinates), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.stats_sensors_heart_rate), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.stats_sensors_cadence), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.stats_sensors_power), true, true, false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, true);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 18);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_max_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_fastest_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_altitude))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_gain))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_loss))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_coordinates))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics_onlyHeartRate() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(200f);
|
||||
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(150f);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 2);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics_onlyCadence() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgCadence()).thenReturn(90f);
|
||||
when(sensorStatisticsMock.getMaxCadence()).thenReturn(110f);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 2);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics_onlyPower() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgPower()).thenReturn(300f);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(200f);
|
||||
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(150f);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgCadence()).thenReturn(90f);
|
||||
when(sensorStatisticsMock.getMaxCadence()).thenReturn(110f);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgPower()).thenReturn(300f);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 5);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
|
||||
}
|
||||
}
|
||||
@@ -73,17 +73,21 @@ limitations under the License.
|
||||
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$ImportDirectoryChooserActivity" />
|
||||
|
||||
<activity android:name=".io.file.importer.ImportActivity">
|
||||
<activity
|
||||
android:name=".io.file.importer.ImportActivity"
|
||||
android:exported="true">
|
||||
|
||||
<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>
|
||||
@@ -142,6 +146,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>
|
||||
@@ -257,7 +262,9 @@ limitations under the License.
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".MarkerDetailActivity">
|
||||
<activity
|
||||
android:name=".MarkerDetailActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="de.dennisguse.opentracks.MarkerDetails" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@@ -268,6 +275,7 @@ limitations under the License.
|
||||
|
||||
<activity
|
||||
android:name=".MarkerListActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/menu_markers"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
@@ -280,6 +288,7 @@ limitations under the License.
|
||||
|
||||
<activity
|
||||
android:name=".TrackListActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/SplashTheme">
|
||||
<intent-filter>
|
||||
@@ -307,14 +316,19 @@ limitations under the License.
|
||||
<activity android:name=".io.file.exporter.ExportActivity" />
|
||||
|
||||
<activity android:name=".settings.SettingsActivity" />
|
||||
<activity android:name=".settings.SettingsCustomLayoutActivity" />
|
||||
|
||||
<activity android:name=".ShowOnMapProxyActivity$KML">
|
||||
<activity
|
||||
android:name=".ShowOnMapProxyActivity$KML"
|
||||
android:exported="true">
|
||||
<intent-filter android:label="@string/open_track_as_KML">
|
||||
<action android:name="Intent.OpenTracks-Dashboard" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".ShowOnMapProxyActivity$GPX">
|
||||
<activity
|
||||
android:name=".ShowOnMapProxyActivity$GPX"
|
||||
android:exported="true">
|
||||
<intent-filter android:label="@string/open_track_as_GPX">
|
||||
<action android:name="Intent.OpenTracks-Dashboard" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@@ -18,8 +18,6 @@ package de.dennisguse.opentracks;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -41,8 +39,6 @@ import de.dennisguse.opentracks.services.TrackDeleteServiceConnection;
|
||||
//TODO Check if this class is still such a good idea; inheritance might limit maintainability
|
||||
public abstract class AbstractTrackDeleteActivity extends AbstractActivity implements ConfirmDeleteCaller, TrackDeleteServiceConnection.Listener {
|
||||
|
||||
private static final String TAG = AbstractTrackDeleteActivity.class.getSimpleName();
|
||||
|
||||
private TrackDeleteServiceConnection trackDeleteServiceConnection;
|
||||
|
||||
@Override
|
||||
@@ -97,10 +93,7 @@ public abstract class AbstractTrackDeleteActivity extends AbstractActivity imple
|
||||
|
||||
protected abstract void onDeleteConfirmed();
|
||||
|
||||
@Nullable
|
||||
protected Track.Id getRecordingTrackId() {
|
||||
return null;
|
||||
}
|
||||
protected abstract Track.Id getRecordingTrackId();
|
||||
|
||||
@Override
|
||||
public void connected() {
|
||||
|
||||
@@ -27,7 +27,6 @@ import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
@@ -393,24 +392,22 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
|
||||
private void requestGPSPermissions() {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
ActivityResultLauncher<String[]> locationPermissionRequest = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
|
||||
Boolean fineLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
|
||||
if (fineLocationGranted == null || !fineLocationGranted) {
|
||||
Boolean coarseLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false);
|
||||
if (fineLocationGranted == null || !fineLocationGranted
|
||||
|| coarseLocationGranted == null || !coarseLocationGranted) {
|
||||
Toast.makeText(this, R.string.permission_gps_failed, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
String[] permissions;
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
|
||||
permissions = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
|
||||
} else {
|
||||
permissions = new String[]{Manifest.permission.ACCESS_FINE_LOCATION};
|
||||
}
|
||||
String[] permissions = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
|
||||
locationPermissionRequest.launch(permissions);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
enum CustomLayoutFieldType {
|
||||
SHORT(0),
|
||||
WIDE(1);
|
||||
|
||||
private final int value;
|
||||
|
||||
CustomLayoutFieldType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int value() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public class MarkerResourceCursorAdapter extends ResourceCursorAdapter implement
|
||||
final long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
final int cacheSize = (int) (Runtime.getRuntime().maxMemory() - usedMemory) / 8;
|
||||
|
||||
memoryCache = new LruCache<String, Bitmap>(cacheSize) {
|
||||
memoryCache = new LruCache<>(cacheSize) {
|
||||
@Override
|
||||
protected int sizeOf(@NonNull String key, @NonNull Bitmap bitmap) {
|
||||
return bitmap.getByteCount();
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
|
||||
public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<SensorDataModel> sensorDataList;
|
||||
private final Context context;
|
||||
|
||||
public SensorsAdapter(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.sensor_item, parent, false);
|
||||
return new SensorsAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SensorsAdapter.ViewHolder viewHolder = (SensorsAdapter.ViewHolder) holder;
|
||||
SensorDataModel sensorDataModel = sensorDataList.get(position);
|
||||
viewHolder.setData(sensorDataModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (sensorDataList == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return sensorDataList.size();
|
||||
}
|
||||
}
|
||||
|
||||
public List<SensorDataModel> swapData(List<SensorDataModel> data) {
|
||||
if (sensorDataList == data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
sensorDataList = data;
|
||||
|
||||
if (data != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView label;
|
||||
final TextView sensorValue;
|
||||
final TextView value;
|
||||
final TextView unit;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
label = itemView.findViewById(R.id.stats_sensor_label);
|
||||
sensorValue = itemView.findViewById(R.id.stats_sensor_sensor_value);
|
||||
value = itemView.findViewById(R.id.stats_sensor_value);
|
||||
unit = itemView.findViewById(R.id.stats_sensor_unit);
|
||||
}
|
||||
|
||||
public void setData(SensorDataModel sensorDataModel) {
|
||||
String sensorName = sensorDataModel.getSensorName();
|
||||
String sensorValue = sensorDataModel.hasValue() ? sensorDataModel.getSensorValue() : context.getString(R.string.value_unknown);
|
||||
|
||||
this.label.setText(context.getString(sensorDataModel.getLabelId()));
|
||||
if (sensorName == null) {
|
||||
this.sensorValue.setVisibility(View.GONE);
|
||||
} else {
|
||||
this.sensorValue.setText(sensorName);
|
||||
}
|
||||
this.value.setText(sensorValue);
|
||||
this.unit.setText(context.getString(sensorDataModel.getUnitId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
|
||||
public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private Layout layout;
|
||||
private final Context context;
|
||||
private final SettingsCustomLayoutItemClickListener itemClickListener;
|
||||
|
||||
public SettingsCustomLayoutAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
||||
this.context = context;
|
||||
this.itemClickListener = itemClickListener;
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
|
||||
return new SettingsCustomLayoutAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SettingsCustomLayoutAdapter.ViewHolder viewHolder = (SettingsCustomLayoutAdapter.ViewHolder) holder;
|
||||
DataField field = layout.getFields().get(position);
|
||||
viewHolder.itemView.setTag(field.getKey());
|
||||
viewHolder.title.setText(field.getTitle());
|
||||
viewHolder.value.setText(StatisticsUtils.emptyValue(context, field.getKey()));
|
||||
|
||||
viewHolder.title.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel) : R.style.StatsLabelHidden);
|
||||
viewHolder.value.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.StatsPrimaryValue : R.style.StatsSecondaryValue) : R.style.StatsValueHidden);
|
||||
viewHolder.statusIcon.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE);
|
||||
viewHolder.statusIcon.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24));
|
||||
viewHolder.moveIcon.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (layout == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return layout.getFields().size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return layout.getFields().get(position).isWide() ? CustomLayoutFieldType.WIDE.value() : CustomLayoutFieldType.SHORT.value();
|
||||
}
|
||||
|
||||
public boolean isItemWide(int position) {
|
||||
return getItemViewType(position) == CustomLayoutFieldType.WIDE.value();
|
||||
}
|
||||
|
||||
public DataField getItem(int position) {
|
||||
return layout.getFields().get(position);
|
||||
}
|
||||
|
||||
public void swapValues(Layout data) {
|
||||
this.layout = data;
|
||||
if (this.layout != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Layout move(int fromPosition, int toPosition) {
|
||||
layout.moveField(fromPosition, toPosition);
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
return layout;
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
final TextView title;
|
||||
final TextView value;
|
||||
final TextView unit;
|
||||
final ImageView statusIcon;
|
||||
final ImageView moveIcon;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
title = itemView.findViewById(R.id.stats_description_main);
|
||||
value = itemView.findViewById(R.id.stats_value);
|
||||
unit = itemView.findViewById(R.id.stats_unit);
|
||||
statusIcon = itemView.findViewById(R.id.stats_icon_show_status);
|
||||
moveIcon = itemView.findViewById(R.id.stats_iv_drag_indicator);
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String statTitle = (String) view.getTag();
|
||||
Optional<DataField> optionalField = layout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst();
|
||||
optionalField.ifPresent(itemClickListener::onSettingsCustomLayoutItemClicked);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SettingsCustomLayoutItemClickListener {
|
||||
void onSettingsCustomLayoutItemClicked(@NonNull DataField field);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
|
||||
public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<StatisticData> statisticDataList;
|
||||
private final Context context;
|
||||
|
||||
public StatisticsAdapter(Context context) {
|
||||
this.context = context;
|
||||
this.statisticDataList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_item, parent, false);
|
||||
return new StatisticsAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder;
|
||||
StatisticData statisticData = statisticDataList.get(position);
|
||||
viewHolder.setData(statisticData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (statisticDataList == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return statisticDataList.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return statisticDataList.get(position).getField().isWide() ? CustomLayoutFieldType.WIDE.value() : CustomLayoutFieldType.SHORT.value();
|
||||
}
|
||||
|
||||
public boolean isItemWide(int position) {
|
||||
return getItemViewType(position) == CustomLayoutFieldType.WIDE.value();
|
||||
}
|
||||
|
||||
public List<StatisticData> swapData(List<StatisticData> data) {
|
||||
if (statisticDataList == data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
statisticDataList = data;
|
||||
|
||||
if (data != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static class WithRecordedLayout extends StatisticsAdapter {
|
||||
public WithRecordedLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_recorded_item, parent, false);
|
||||
return new StatisticsAdapter.ViewRecordedHolder(view);
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView value;
|
||||
final TextView unit;
|
||||
final TextView descMain;
|
||||
final TextView descSecondary;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
value = itemView.findViewById(R.id.stats_value);
|
||||
unit = itemView.findViewById(R.id.stats_unit);
|
||||
descMain = itemView.findViewById(R.id.stats_description_main);
|
||||
descSecondary = itemView.findViewById(R.id.stats_description_secondary);
|
||||
}
|
||||
|
||||
public void setData(StatisticData statisticData) {
|
||||
if (statisticData == null) {
|
||||
return;
|
||||
}
|
||||
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
|
||||
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryValue : R.style.StatsSecondaryValue);
|
||||
this.unit.setText(statisticData.getUnit());
|
||||
this.descMain.setText(statisticData.getField().getTitle());
|
||||
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel);
|
||||
if (statisticData.hasDescription()) {
|
||||
this.descSecondary.setVisibility(View.VISIBLE);
|
||||
this.descSecondary.setText(statisticData.getDescription());
|
||||
} else {
|
||||
this.descSecondary.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewRecordedHolder extends ViewHolder {
|
||||
|
||||
public ViewRecordedHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public void setData(StatisticData statisticData) {
|
||||
if (statisticData == null) {
|
||||
return;
|
||||
}
|
||||
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
|
||||
this.unit.setText(statisticData.getUnit());
|
||||
this.descMain.setText(statisticData.getField().getTitle());
|
||||
if (statisticData.hasDescription()) {
|
||||
this.descSecondary.setVisibility(View.VISIBLE);
|
||||
this.descSecondary.setText(statisticData.getDescription());
|
||||
} else {
|
||||
this.descSecondary.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,12 +27,10 @@ import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Altitude;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
@@ -43,8 +41,8 @@ import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.EGM2008Utils;
|
||||
|
||||
/**
|
||||
* Track data hub.
|
||||
@@ -76,7 +74,7 @@ public class TrackDataHub {
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final int targetNumPoints;
|
||||
|
||||
private EGM2008Utils.EGM2008Correction egm2008Correction;
|
||||
private final EGM2008CorrectionManager egm2008Correction = new EGM2008CorrectionManager();
|
||||
|
||||
//TODO Check if this is needed.
|
||||
private HandlerThread handlerThread;
|
||||
@@ -209,13 +207,6 @@ public class TrackDataHub {
|
||||
return selectedTrackId != null && selectedTrackId.equals(recordingStatus.getTrackId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the selected track is paused.
|
||||
*/
|
||||
public boolean isSelectedTrackPaused() {
|
||||
return selectedTrackId != null && selectedTrackId.equals(recordingStatus.getTrackId()) && recordingStatus.isPaused();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data for all listeners. To be run in the {@link #handler} thread.
|
||||
*/
|
||||
@@ -360,7 +351,7 @@ public class TrackDataHub {
|
||||
break;
|
||||
}
|
||||
|
||||
correctAltitude(trackPoint);
|
||||
egm2008Correction.correctAltitude(context, trackPoint);
|
||||
|
||||
if (localFirstSeenTrackPointId == null) {
|
||||
localFirstSeenTrackPointId = trackPointId;
|
||||
@@ -401,23 +392,7 @@ public class TrackDataHub {
|
||||
listeners.stream().forEach(TrackDataListener::onNewTrackPointsDone);
|
||||
}
|
||||
|
||||
private void correctAltitude(TrackPoint trackPoint) {
|
||||
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
|
||||
Log.d(TAG, "No altitude correction necessary.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (egm2008Correction == null || !egm2008Correction.canCorrect(trackPoint.getLocation())) {
|
||||
try {
|
||||
egm2008Correction = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Could not load altitude correction for " + trackPoint, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
trackPoint.setAltitude(Altitude.EGM2008.of(egm2008Correction.correctAltitude(trackPoint.getLocation())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the track points sampling states.
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.CsvConstants;
|
||||
|
||||
public class DataField {
|
||||
public static final String YES_VALUE = "1";
|
||||
public static final String NOT_VALUE = "0";
|
||||
|
||||
private final String key;
|
||||
private final String title;
|
||||
private boolean isVisible;
|
||||
private boolean isPrimary;
|
||||
private final boolean isWide;
|
||||
|
||||
public DataField(String key, String title, boolean isVisible, boolean isPrimary, boolean isWide) {
|
||||
this.key = key;
|
||||
this.title = title;
|
||||
this.isVisible = isVisible;
|
||||
this.isPrimary = isPrimary;
|
||||
this.isWide = isWide;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
public void toggleVisibility() {
|
||||
isVisible = !isVisible;
|
||||
}
|
||||
|
||||
public boolean isPrimary() {
|
||||
return isPrimary;
|
||||
}
|
||||
|
||||
public void togglePrimary() {
|
||||
isPrimary = !isPrimary;
|
||||
}
|
||||
|
||||
public boolean isWide() {
|
||||
return isWide;
|
||||
}
|
||||
|
||||
public String toCsv() {
|
||||
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
|
||||
String primary = this.isPrimary ? YES_VALUE : NOT_VALUE;
|
||||
return key + CsvConstants.ITEM_SEPARATOR + visible + CsvConstants.ITEM_SEPARATOR + primary;
|
||||
}
|
||||
|
||||
public static String getTitleByKey(Context context, String key) {
|
||||
if (key.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
|
||||
return context.getString(R.string.stats_total_time);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
|
||||
return context.getString(R.string.stats_moving_time);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
|
||||
return context.getString(R.string.stats_distance);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_speed_key))) {
|
||||
return context.getString(R.string.stats_speed);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
|
||||
return context.getString(R.string.stats_pace);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
|
||||
return context.getString(R.string.stats_average_moving_speed);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
|
||||
return context.getString(R.string.stats_average_speed);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
|
||||
return context.getString(R.string.stats_max_speed);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
|
||||
return context.getString(R.string.stats_average_moving_pace);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
|
||||
return context.getString(R.string.stats_average_pace);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
|
||||
return context.getString(R.string.stats_fastest_pace);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
|
||||
return context.getString(R.string.stats_altitude);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
|
||||
return context.getString(R.string.stats_gain);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
|
||||
return context.getString(R.string.stats_loss);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
|
||||
return context.getString(R.string.stats_coordinates);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
|
||||
return context.getString(R.string.stats_sensors_heart_rate);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
|
||||
return context.getString(R.string.stats_sensors_cadence);
|
||||
} else if (key.equals(context.getString(R.string.stats_custom_layout_power_key))) {
|
||||
return context.getString(R.string.stats_sensors_power);
|
||||
} else {
|
||||
throw new RuntimeException("It doesn't exists a field with key: " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,15 @@ public class Distance {
|
||||
}
|
||||
|
||||
public static Distance ofMM(double distance_mm) {
|
||||
return of(distance_mm * UnitConversions.MM_TO_M);
|
||||
return of(0.001 * distance_mm);
|
||||
}
|
||||
|
||||
public static Distance ofCM(double distance_cm) {
|
||||
return of(0.01 * distance_cm);
|
||||
}
|
||||
|
||||
public static Distance ofDM(double distance_dm) {
|
||||
return of(0.1 * distance_dm);
|
||||
}
|
||||
|
||||
public static Distance one(boolean metricUnit) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Layout {
|
||||
private final String profile;
|
||||
private final List<DataField> dataFields = new ArrayList<>();
|
||||
|
||||
public Layout(String profile) {
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) {
|
||||
dataFields.add(new DataField(key, title, visible, primary, isWide));
|
||||
}
|
||||
|
||||
public void addField(DataField dataField) {
|
||||
dataFields.add(dataField);
|
||||
}
|
||||
|
||||
public void addFields(List<DataField> dataFields) {
|
||||
this.dataFields.addAll(dataFields);
|
||||
}
|
||||
|
||||
public void removeField(DataField dataField) {
|
||||
dataFields.remove(dataField);
|
||||
}
|
||||
|
||||
public List<DataField> getFields() {
|
||||
return new ArrayList<>(dataFields);
|
||||
}
|
||||
|
||||
public void moveField(int from, int to) {
|
||||
DataField dataFieldToMove = dataFields.remove(from);
|
||||
dataFields.add(to, dataFieldToMove);
|
||||
}
|
||||
|
||||
public String getProfile() {
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,8 @@ public final class Marker {
|
||||
private final Instant time;
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private Float accuracy;
|
||||
@Deprecated //Not needed
|
||||
private Distance accuracy;
|
||||
private Altitude altitude;
|
||||
private Float bearing;
|
||||
|
||||
@@ -96,7 +97,7 @@ public final class Marker {
|
||||
public void setTrackPoint(TrackPoint trackPoint) {
|
||||
this.latitude = trackPoint.getLatitude();
|
||||
this.longitude = trackPoint.getLongitude();
|
||||
if (trackPoint.hasAccuracy()) this.accuracy = trackPoint.getAccuracy();
|
||||
if (trackPoint.hasHorizontalAccuracy()) this.accuracy = trackPoint.getHorizontalAccuracy();
|
||||
if (trackPoint.hasAltitude()) this.altitude = trackPoint.getAltitude();
|
||||
if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing();
|
||||
}
|
||||
@@ -173,7 +174,7 @@ public final class Marker {
|
||||
location.setBearing(bearing);
|
||||
}
|
||||
if (hasAccuracy()) {
|
||||
location.setAccuracy(accuracy);
|
||||
location.setAccuracy((float) accuracy.toM());
|
||||
}
|
||||
if (hasAltitude()) {
|
||||
location.setAltitude(altitude.toM());
|
||||
@@ -202,11 +203,11 @@ public final class Marker {
|
||||
return accuracy != null;
|
||||
}
|
||||
|
||||
public Float getAccuracy() {
|
||||
public Distance getAccuracy() {
|
||||
return accuracy;
|
||||
}
|
||||
|
||||
public void setAccuracy(Float accuracy) {
|
||||
public void setAccuracy(Distance accuracy) {
|
||||
this.accuracy = accuracy;
|
||||
}
|
||||
|
||||
@@ -308,7 +309,7 @@ public final class Marker {
|
||||
parcel.writeLong(id);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Id> CREATOR = new Parcelable.Creator<Id>() {
|
||||
public static final Parcelable.Creator<Id> CREATOR = new Parcelable.Creator<>() {
|
||||
public Id createFromParcel(Parcel in) {
|
||||
return new Id(in.readLong());
|
||||
}
|
||||
|
||||
@@ -23,10 +23,6 @@ public class Speed {
|
||||
return of(Float.parseFloat(speed_mps));
|
||||
}
|
||||
|
||||
public static Speed invalid() {
|
||||
return of(Double.NaN);
|
||||
}
|
||||
|
||||
public static Speed zero() {
|
||||
return of(0.0);
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ public class Track {
|
||||
parcel.writeLong(id);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Track.Id> CREATOR = new Parcelable.Creator<Track.Id>() {
|
||||
public static final Parcelable.Creator<Track.Id> CREATOR = new Parcelable.Creator<>() {
|
||||
public Track.Id createFromParcel(Parcel in) {
|
||||
return new Track.Id(in.readLong());
|
||||
}
|
||||
|
||||
@@ -49,11 +49,11 @@ public class TrackPoint {
|
||||
private TrackPoint.Id id;
|
||||
|
||||
@NonNull
|
||||
private Instant time;
|
||||
private final Instant time;
|
||||
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private Float accuracy;
|
||||
private Distance horizontalAccuracy;
|
||||
private Altitude altitude;
|
||||
private Speed speed;
|
||||
private Float bearing;
|
||||
@@ -96,18 +96,13 @@ public class TrackPoint {
|
||||
private Float altitudeGain_m = null;
|
||||
private Float altitudeLoss_m = null;
|
||||
|
||||
public TrackPoint(@NonNull Type type) {
|
||||
public TrackPoint(@NonNull Type type, @NonNull Instant time) {
|
||||
this.type = type;
|
||||
this.time = Instant.now(); //TODO This may result in #800
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull Type type, Instant time) {
|
||||
this(type);
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull Location location) {
|
||||
this(Type.TRACKPOINT, location, Instant.now()); //TODO This may result in #800
|
||||
public TrackPoint(@NonNull Location location, @NonNull Instant time) {
|
||||
this(Type.TRACKPOINT, location, time);
|
||||
}
|
||||
|
||||
public TrackPoint(@NonNull Type type, @NonNull Location location, @NonNull Instant time) {
|
||||
@@ -117,7 +112,7 @@ public class TrackPoint {
|
||||
this.longitude = location.getLongitude();
|
||||
this.altitude = location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null;
|
||||
this.speed = location.hasSpeed() ? Speed.of(location.getSpeed()) : null;
|
||||
this.accuracy = location.hasAccuracy() ? location.getAccuracy() : null;
|
||||
this.horizontalAccuracy = location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null;
|
||||
|
||||
//TODO Should we copy the bearing?
|
||||
}
|
||||
@@ -130,28 +125,10 @@ public class TrackPoint {
|
||||
this.altitude = altitude;
|
||||
}
|
||||
|
||||
@Deprecated //See #316
|
||||
public static TrackPoint createSegmentStartManual() {
|
||||
return createSegmentStartManualWithTime(Instant.now());
|
||||
}
|
||||
|
||||
public static TrackPoint createSegmentStartManualWithTime(Instant time) {
|
||||
return new TrackPoint(Type.SEGMENT_START_MANUAL, time);
|
||||
}
|
||||
|
||||
@Deprecated //See #316
|
||||
public static TrackPoint createSegmentStartAutomatic() {
|
||||
return createSegmentStartAutomaticWithTime(Instant.now());
|
||||
}
|
||||
|
||||
public static TrackPoint createSegmentStartAutomaticWithTime(Instant time) {
|
||||
return new TrackPoint(Type.SEGMENT_START_AUTOMATIC, time);
|
||||
}
|
||||
|
||||
public static TrackPoint createSegmentEnd() {
|
||||
return createSegmentEndWithTime(Instant.now());
|
||||
}
|
||||
|
||||
public static TrackPoint createSegmentEndWithTime(Instant time) {
|
||||
return new TrackPoint(Type.SEGMENT_END_MANUAL, time);
|
||||
}
|
||||
@@ -220,12 +197,15 @@ public class TrackPoint {
|
||||
if (hasBearing()) {
|
||||
location.setBearing(bearing);
|
||||
}
|
||||
if (hasAccuracy()) {
|
||||
location.setAccuracy(accuracy);
|
||||
if (hasHorizontalAccuracy()) {
|
||||
location.setAccuracy((float) horizontalAccuracy.toM());
|
||||
}
|
||||
if (hasAltitude()) {
|
||||
location.setAltitude(altitude.toM());
|
||||
}
|
||||
if (hasSpeed()) {
|
||||
location.setSpeed((float) speed.toMPS());
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
@@ -256,15 +236,11 @@ public class TrackPoint {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Instant getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public TrackPoint setTime(Instant time) {
|
||||
this.time = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isRecent() {
|
||||
return Instant.now()
|
||||
.isBefore(time.plus(MAX_LOCATION_AGE));
|
||||
@@ -320,33 +296,33 @@ public class TrackPoint {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasAccuracy() {
|
||||
return accuracy != null;
|
||||
public boolean hasHorizontalAccuracy() {
|
||||
return horizontalAccuracy != null;
|
||||
}
|
||||
|
||||
public float getAccuracy() {
|
||||
return accuracy;
|
||||
public Distance getHorizontalAccuracy() {
|
||||
return horizontalAccuracy;
|
||||
}
|
||||
|
||||
public TrackPoint setAccuracy(float horizontalAccuracy) {
|
||||
this.accuracy = horizontalAccuracy;
|
||||
public TrackPoint setHorizontalAccuracy(Distance horizontalAccuracy) {
|
||||
this.horizontalAccuracy = horizontalAccuracy;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
|
||||
public Distance distanceToPrevious(TrackPoint previous) {
|
||||
if (hasSensorDistance()) {
|
||||
return getSensorDistance();
|
||||
}
|
||||
if (!(hasLocation() && previous.hasLocation())) {
|
||||
if (previous == null || !(hasLocation() && previous.hasLocation())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Distance.of(getLocation().distanceTo(previous.getLocation()));
|
||||
}
|
||||
|
||||
public boolean fulfillsAccuracy(int poorAccuracy) {
|
||||
return hasAccuracy() && accuracy < poorAccuracy;
|
||||
public boolean fulfillsAccuracy(Distance thresholdHorizontalAccuracy) {
|
||||
return hasHorizontalAccuracy() && horizontalAccuracy.lessThan(thresholdHorizontalAccuracy);
|
||||
}
|
||||
|
||||
//TODO Bearing requires a location; what do we do if we don't have any?
|
||||
@@ -424,11 +400,11 @@ public class TrackPoint {
|
||||
return result;
|
||||
}
|
||||
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
|
||||
if (!hasAccuracy()) {
|
||||
if (!hasHorizontalAccuracy()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return result + " acc=" + getAccuracy();
|
||||
return result + " acc=" + getHorizontalAccuracy();
|
||||
}
|
||||
|
||||
public static class Id {
|
||||
|
||||
@@ -45,7 +45,7 @@ public interface TrackPointsColumns extends BaseColumns {
|
||||
|
||||
String TIME = "time";
|
||||
String ALTITUDE = "elevation";
|
||||
String ACCURACY = "accuracy";
|
||||
String HORIZONTAL_ACCURACY = "accuracy";
|
||||
String SPEED = "speed";
|
||||
String BEARING = "bearing";
|
||||
String SENSOR_HEARTRATE = "sensor_heartrate";
|
||||
@@ -69,7 +69,7 @@ public interface TrackPointsColumns extends BaseColumns {
|
||||
+ LATITUDE + " INTEGER, "
|
||||
+ TIME + " INTEGER, "
|
||||
+ ALTITUDE + " FLOAT, "
|
||||
+ ACCURACY + " FLOAT, "
|
||||
+ HORIZONTAL_ACCURACY + " FLOAT, "
|
||||
+ SPEED + " FLOAT, "
|
||||
+ BEARING + " FLOAT, "
|
||||
+ SENSOR_HEARTRATE + " FLOAT, "
|
||||
|
||||
@@ -31,7 +31,7 @@ class CachedTrackPointsIndexes {
|
||||
latitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
|
||||
timeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME);
|
||||
altitudeIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE);
|
||||
accuracyIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
|
||||
accuracyIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.HORIZONTAL_ACCURACY);
|
||||
speedIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
|
||||
bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
|
||||
sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
|
||||
|
||||
@@ -243,9 +243,9 @@ public class ContentProviderUtils {
|
||||
* @param track the track
|
||||
* @return the content provider URI of the inserted track.
|
||||
*/
|
||||
///TODO Return Track.Id
|
||||
public Uri insertTrack(Track track) {
|
||||
return contentResolver.insert(TracksColumns.CONTENT_URI, createContentValues(track));
|
||||
public Track.Id insertTrack(Track track) {
|
||||
Uri uri = contentResolver.insert(TracksColumns.CONTENT_URI, createContentValues(track));
|
||||
return new Track.Id(ContentUris.parseId(uri));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,6 +290,31 @@ public class ContentProviderUtils {
|
||||
return values;
|
||||
}
|
||||
|
||||
public void updateTrackStatistics(@NonNull Track.Id trackId, @NonNull TrackStatistics trackStatistics) {
|
||||
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(trackStatistics), TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())});
|
||||
}
|
||||
|
||||
private ContentValues createContentValues(TrackStatistics trackStatistics) {
|
||||
ContentValues values = new ContentValues();
|
||||
if (trackStatistics.getStartTime() != null) {
|
||||
values.put(TracksColumns.STARTTIME, trackStatistics.getStartTime().toEpochMilli());
|
||||
}
|
||||
if (trackStatistics.getStopTime() != null) {
|
||||
values.put(TracksColumns.STOPTIME, trackStatistics.getStopTime().toEpochMilli());
|
||||
}
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance().toM());
|
||||
values.put(TracksColumns.TOTALTIME, trackStatistics.getTotalTime().toMillis());
|
||||
values.put(TracksColumns.MOVINGTIME, trackStatistics.getMovingTime().toMillis());
|
||||
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed().toMPS());
|
||||
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
|
||||
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
|
||||
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.getTotalAltitudeGain());
|
||||
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.getTotalAltitudeLoss());
|
||||
return values;
|
||||
}
|
||||
|
||||
public Marker createMarker(Cursor cursor) {
|
||||
int idIndex = cursor.getColumnIndexOrThrow(MarkerColumns._ID);
|
||||
int nameIndex = cursor.getColumnIndexOrThrow(MarkerColumns.NAME);
|
||||
@@ -318,7 +343,7 @@ public class ContentProviderUtils {
|
||||
marker.setAltitude(Altitude.WGS84.of(cursor.getFloat(altitudeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(accuracyIndex)) {
|
||||
marker.setAccuracy(cursor.getFloat(accuracyIndex));
|
||||
marker.setAccuracy(Distance.of(cursor.getFloat(accuracyIndex)));
|
||||
}
|
||||
if (!cursor.isNull(bearingIndex)) {
|
||||
marker.setBearing(cursor.getFloat(bearingIndex));
|
||||
@@ -472,7 +497,7 @@ public class ContentProviderUtils {
|
||||
values.put(MarkerColumns.ALTITUDE, marker.getAltitude().toM());
|
||||
}
|
||||
if (marker.hasAccuracy()) {
|
||||
values.put(MarkerColumns.ACCURACY, marker.getAccuracy());
|
||||
values.put(MarkerColumns.ACCURACY, marker.getAccuracy().toM());
|
||||
}
|
||||
if (marker.hasBearing()) {
|
||||
values.put(MarkerColumns.BEARING, marker.getBearing());
|
||||
@@ -506,7 +531,8 @@ public class ContentProviderUtils {
|
||||
* @param indexes the cached trackPoints indexes
|
||||
*/
|
||||
static TrackPoint fillTrackPoint(Cursor cursor, CachedTrackPointsIndexes indexes) {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.getById(cursor.getInt(indexes.typeIndex)));
|
||||
Instant time = Instant.ofEpochMilli(cursor.getLong(indexes.timeIndex));
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.getById(cursor.getInt(indexes.typeIndex)), time);
|
||||
trackPoint.setId(new TrackPoint.Id(cursor.getInt(indexes.idIndex)));
|
||||
|
||||
if (!cursor.isNull(indexes.longitudeIndex)) {
|
||||
@@ -515,14 +541,11 @@ public class ContentProviderUtils {
|
||||
if (!cursor.isNull(indexes.latitudeIndex)) {
|
||||
trackPoint.setLatitude(((double) cursor.getInt(indexes.latitudeIndex)) / 1E6);
|
||||
}
|
||||
if (!cursor.isNull(indexes.timeIndex)) {
|
||||
trackPoint.setTime(Instant.ofEpochMilli(cursor.getLong(indexes.timeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.altitudeIndex)) {
|
||||
trackPoint.setAltitude(Altitude.WGS84.of(cursor.getFloat(indexes.altitudeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.accuracyIndex)) {
|
||||
trackPoint.setAccuracy(cursor.getFloat(indexes.accuracyIndex));
|
||||
trackPoint.setHorizontalAccuracy(Distance.of(cursor.getFloat(indexes.accuracyIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.speedIndex)) {
|
||||
trackPoint.setSpeed(Speed.of(cursor.getFloat(indexes.speedIndex)));
|
||||
@@ -683,8 +706,8 @@ public class ContentProviderUtils {
|
||||
if (trackPoint.hasAltitude()) {
|
||||
values.put(TrackPointsColumns.ALTITUDE, trackPoint.getAltitude().toM());
|
||||
}
|
||||
if (trackPoint.hasAccuracy()) {
|
||||
values.put(TrackPointsColumns.ACCURACY, trackPoint.getAccuracy());
|
||||
if (trackPoint.hasHorizontalAccuracy()) {
|
||||
values.put(TrackPointsColumns.HORIZONTAL_ACCURACY, trackPoint.getHorizontalAccuracy().toM());
|
||||
}
|
||||
if (trackPoint.hasSpeed()) {
|
||||
values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed().toMPS());
|
||||
|
||||
@@ -49,15 +49,13 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
private static final String TAG = ShareContentProvider.class.getSimpleName();
|
||||
|
||||
private static final int URI_GPX = 0;
|
||||
private static final int URI_KML_ONLY = 1;
|
||||
private static final int URI_KML_WITH_TRACKDETAIL = 2;
|
||||
private static final int URI_KML_WITH_TRACKDETAIL_SENSORDATA = 3;
|
||||
|
||||
private static final int URI_KMZ_ONLY_TRACK = 4;
|
||||
private static final int URI_KMZ_WITH_TRACKDETAIL = 5;
|
||||
private static final int URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA = 6;
|
||||
private static final int URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES = 7;
|
||||
|
||||
private static final int URI_SHARE_PICTURE = 8;
|
||||
|
||||
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
private static final String TRACKID_DELIMITER = "_";
|
||||
|
||||
@@ -68,14 +66,22 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA);
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
|
||||
|
||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.SHARE_PICTURE_PNG.getName() + "/*/*", URI_SHARE_PICTURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An URI for one file containing this tracks.
|
||||
*/
|
||||
public static Pair<Uri, String> createURI(Track.Id trackId, String trackName, @NonNull TrackFileFormat trackFileFormat) {
|
||||
Set<Track.Id> trackIds = new HashSet<>(1);
|
||||
trackIds.add(trackId);
|
||||
return createURI(trackIds, trackName, trackFileFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An URI for one file containing all tracks.
|
||||
*/
|
||||
public static Pair<Uri, String> createURI(Set<Track.Id> trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
|
||||
if (trackIds.isEmpty()) {
|
||||
throw new UnsupportedOperationException();
|
||||
@@ -142,6 +148,9 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES:
|
||||
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
|
||||
|
||||
case URI_SHARE_PICTURE:
|
||||
return TrackFileFormat.SHARE_PICTURE_PNG;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
|
||||
}
|
||||
@@ -167,12 +176,15 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
Object[] values = new Object[projection.length];
|
||||
int i = 0;
|
||||
for (String col : projection) {
|
||||
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
|
||||
switch (col) {
|
||||
case OpenableColumns.DISPLAY_NAME:
|
||||
cols[i] = OpenableColumns.DISPLAY_NAME;
|
||||
values[i++] = uri.getLastPathSegment();
|
||||
} else if (OpenableColumns.SIZE.equals(col)) {
|
||||
break;
|
||||
case OpenableColumns.SIZE:
|
||||
cols[i] = OpenableColumns.SIZE;
|
||||
values[i++] = -1;
|
||||
values[i++] = -1; //Report unknown size; if applications need to know, one need to generate the file here also (count bytes that are written to OutputStream.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +225,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
|
||||
PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||
// TODO handle failure (i.e., do not export an empty file)
|
||||
trackExporter.writeTrack(tracks.toArray(new Track[0]), fileOutputStream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "there occurred an error while sharing a file: " + e);
|
||||
|
||||
@@ -169,9 +169,9 @@ public final class SensorDataCycling {
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public final Distance distance;
|
||||
public final Distance distanceOverall;
|
||||
public final Speed speed;
|
||||
private final Distance distance;
|
||||
private final Distance distanceOverall;
|
||||
private final Speed speed;
|
||||
|
||||
private Data(Distance distance, Distance distanceOverall, Speed speed) {
|
||||
this.distance = distance;
|
||||
@@ -179,12 +179,24 @@ public final class SensorDataCycling {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
public Distance getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public Distance getDistanceOverall() {
|
||||
return distanceOverall;
|
||||
}
|
||||
|
||||
public Speed getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data{" +
|
||||
"distance_m=" + distance +
|
||||
", distance_overall_m=" + distanceOverall +
|
||||
", speed_mps=" + speed +
|
||||
"distance=" + getDistance() +
|
||||
", distance_overall=" + getDistanceOverall() +
|
||||
", speed=" + getSpeed() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package de.dennisguse.opentracks.content.sensor;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
|
||||
/**
|
||||
* Provides cadence in rpm and speed in milliseconds from Bluetooth LE Running Speed and Cadence sensors.
|
||||
*/
|
||||
public final class SensorDataRunning extends SensorData<SensorDataRunning.Data> {
|
||||
|
||||
private static final String TAG = SensorDataRunning.class.getSimpleName();
|
||||
|
||||
private final Speed speed;
|
||||
|
||||
private final Float cadence;
|
||||
|
||||
private final Distance totalDistance;
|
||||
|
||||
public SensorDataRunning(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
this.speed = null;
|
||||
this.cadence = null;
|
||||
this.totalDistance = null;
|
||||
}
|
||||
|
||||
public SensorDataRunning(String sensorAddress, String sensorName, Speed speed, Float cadence, Distance totalDistance) {
|
||||
super(sensorAddress, sensorName);
|
||||
this.speed = speed;
|
||||
this.cadence = cadence;
|
||||
this.totalDistance = totalDistance;
|
||||
}
|
||||
|
||||
private boolean hasTotalDistance() {
|
||||
return totalDistance != null;
|
||||
}
|
||||
|
||||
|
||||
public Float getCadence() {
|
||||
return cadence;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Speed getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Distance getTotalDistance() {
|
||||
return totalDistance;
|
||||
}
|
||||
|
||||
public void compute(SensorDataRunning previous) {
|
||||
Distance overallDistance = null;
|
||||
if (hasTotalDistance() && previous != null && previous.hasTotalDistance()) {
|
||||
overallDistance = this.totalDistance.minus(previous.totalDistance);
|
||||
if (previous.hasValue() && previous.getValue().getDistance() != null) {
|
||||
overallDistance = overallDistance.plus(previous.getValue().getDistance());
|
||||
}
|
||||
}
|
||||
|
||||
value = new Data(speed, cadence, overallDistance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
if (value != null) {
|
||||
value = new Data(value.speed, value.cadence, Distance.of(0));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
private final Speed speed;
|
||||
private final Float cadence;
|
||||
|
||||
@Nullable
|
||||
private final Distance distance;
|
||||
|
||||
public Data(Speed speed, Float cadence, @Nullable Distance distance) {
|
||||
this.speed = speed;
|
||||
this.cadence = cadence;
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
public Speed getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public Float getCadence() {
|
||||
return cadence;
|
||||
}
|
||||
|
||||
public Distance getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data{" +
|
||||
"speed=" + speed +
|
||||
", cadence=" + cadence +
|
||||
", distance=" + distance +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ public final class SensorDataSet {
|
||||
|
||||
private SensorDataCyclingPower cyclingPower;
|
||||
|
||||
private SensorDataRunning runningDistanceSpeedCadence;
|
||||
|
||||
public SensorDataSet() {
|
||||
}
|
||||
|
||||
@@ -22,6 +24,7 @@ public final class SensorDataSet {
|
||||
this.cyclingCadence = toCopy.cyclingCadence;
|
||||
this.cyclingDistanceSpeed = toCopy.cyclingDistanceSpeed;
|
||||
this.cyclingPower = toCopy.cyclingPower;
|
||||
this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence;
|
||||
}
|
||||
|
||||
public SensorDataHeartRate getHeartRate() {
|
||||
@@ -40,6 +43,10 @@ public final class SensorDataSet {
|
||||
return cyclingPower;
|
||||
}
|
||||
|
||||
public SensorDataRunning getRunningDistanceSpeedCadence() {
|
||||
return runningDistanceSpeedCadence;
|
||||
}
|
||||
|
||||
public void set(SensorData<?> data) {
|
||||
set(data, data);
|
||||
}
|
||||
@@ -53,6 +60,7 @@ public final class SensorDataSet {
|
||||
this.cyclingCadence = null;
|
||||
this.cyclingDistanceSpeed = null;
|
||||
this.cyclingPower = null;
|
||||
this.runningDistanceSpeedCadence = null;
|
||||
}
|
||||
|
||||
public void fillTrackPoint(TrackPoint trackPoint) {
|
||||
@@ -65,13 +73,19 @@ public final class SensorDataSet {
|
||||
}
|
||||
|
||||
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
|
||||
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distanceOverall);
|
||||
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed);
|
||||
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().getDistanceOverall());
|
||||
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().getSpeed());
|
||||
}
|
||||
|
||||
if (cyclingPower != null && cyclingPower.hasValue()) {
|
||||
trackPoint.setPower(cyclingPower.getValue());
|
||||
}
|
||||
|
||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue()) {
|
||||
trackPoint.setSensorDistance(runningDistanceSpeedCadence.getValue().getDistance());
|
||||
trackPoint.setSpeed(runningDistanceSpeedCadence.getValue().getSpeed());
|
||||
trackPoint.setCyclingCadence_rpm(runningDistanceSpeedCadence.getValue().getCadence());
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
@@ -79,6 +93,8 @@ public final class SensorDataSet {
|
||||
if (cyclingCadence != null) cyclingCadence.reset();
|
||||
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
|
||||
if (cyclingPower != null) cyclingPower.reset();
|
||||
|
||||
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -87,7 +103,8 @@ public final class SensorDataSet {
|
||||
return (getHeartRate() != null ? "" + getHeartRate() : "")
|
||||
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
|
||||
+ (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
|
||||
+ (getCyclingPower() != null ? " " + getCyclingPower() : "");
|
||||
+ (getCyclingPower() != null ? " " + getCyclingPower() : "")
|
||||
+ (getRunningDistanceSpeedCadence() != null ? " " + getRunningDistanceSpeedCadence() : "");
|
||||
}
|
||||
|
||||
private void set(@NonNull SensorData<?> type, SensorData<?> data) {
|
||||
@@ -110,6 +127,11 @@ public final class SensorDataSet {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type instanceof SensorDataRunning) {
|
||||
this.runningDistanceSpeedCadence = (SensorDataRunning) data;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException(type.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public class IntervalsFragment extends Fragment {
|
||||
// TODO handle empty view: before we did viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
|
||||
viewBinding.intervalList.setAdapter(adapter);
|
||||
|
||||
spinnerAdapter = new ArrayAdapter<IntervalStatisticsModel.IntervalOption>(getContext(), android.R.layout.simple_spinner_dropdown_item, IntervalStatisticsModel.IntervalOption.values()) {
|
||||
spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, IntervalStatisticsModel.IntervalOption.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
|
||||
@@ -254,9 +254,9 @@ public class MarkerDetailFragment extends Fragment {
|
||||
|
||||
private void setLocation(boolean addShadow) {
|
||||
String value = "[" + getString(R.string.stats_latitude) + " "
|
||||
+ StringUtils.formatCoordinate(marker.getLatitude()) + ", "
|
||||
+ StringUtils.formatCoordinate(getContext(), marker.getLatitude()) + ", "
|
||||
+ getString(R.string.stats_longitude) + " "
|
||||
+ StringUtils.formatCoordinate(marker.getLongitude()) + "]";
|
||||
+ StringUtils.formatCoordinate(getContext(), marker.getLongitude()) + "]";
|
||||
|
||||
ListItemUtils.setTextView(getActivity(), viewBinding.markerDetailMarkerLocation, value, addShadow);
|
||||
}
|
||||
|
||||
@@ -31,12 +31,11 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.adapters.SensorsAdapter;
|
||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
|
||||
@@ -45,7 +44,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticDataBuilder;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user for a recorded {@link Track}.
|
||||
@@ -69,7 +69,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
}
|
||||
|
||||
private SensorStatistics sensorStatistics;
|
||||
private SensorsAdapter sensorsAdapter;
|
||||
private StatisticsAdapter.WithRecordedLayout sensorsAdapter;
|
||||
|
||||
private Track.Id trackId;
|
||||
@Nullable // Lazily loaded.
|
||||
@@ -114,7 +114,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
|
||||
sensorsAdapter = new SensorsAdapter(getContext());
|
||||
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -238,10 +238,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
// Set altitude gain and loss
|
||||
{
|
||||
// Make altitude visible?
|
||||
boolean show = PreferencesUtils.isShowStatsAltitude(sharedPreferences, getContext());
|
||||
viewBinding.statsAltitudeGroup.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
|
||||
Float altitudeGain_m = trackStatistics.getTotalAltitudeGain();
|
||||
Float altitudeLoss_m = trackStatistics.getTotalAltitudeLoss();
|
||||
|
||||
@@ -254,6 +250,9 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
parts = StringUtils.getAltitudeParts(getContext(), altitudeLoss_m, preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeLossValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeLossUnit.setText(parts.second);
|
||||
|
||||
boolean show = altitudeGain_m != null && altitudeLoss_m != null;
|
||||
viewBinding.statsAltitudeGroup.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,18 +261,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
List<SensorDataModel> sensorDataList = new ArrayList<>();
|
||||
if (sensorStatistics.hasHeartRate()) {
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_heart_rate_max, R.string.sensor_unit_beats_per_minute, sensorStatistics.getMaxHeartRate()));
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_heart_rate_avg, R.string.sensor_unit_beats_per_minute, sensorStatistics.getAvgHeartRate()));
|
||||
}
|
||||
if (sensorStatistics.hasCadence()) {
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_cadence_max, R.string.sensor_unit_rounds_per_minute, sensorStatistics.getMaxCadence()));
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_cadence_avg, R.string.sensor_unit_rounds_per_minute, sensorStatistics.getAvgCadence()));
|
||||
}
|
||||
if (sensorStatistics.hasPower()) {
|
||||
sensorDataList.add(new SensorDataModel(R.string.sensor_state_power_avg, R.string.sensor_unit_power, sensorStatistics.getAvgPower()));
|
||||
}
|
||||
List<StatisticData> sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics);
|
||||
if (sensorDataList.size() > 0) {
|
||||
sensorsAdapter.swapData(sensorDataList);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.fragments;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -11,28 +10,25 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.adapters.SensorsAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user for a currently recording {@link Track}.
|
||||
@@ -51,15 +47,16 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private TrackRecordingService.RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
|
||||
private TrackPoint latestTrackPoint;
|
||||
private Layout layout;
|
||||
|
||||
private StatisticsRecordingBinding viewBinding;
|
||||
private SensorsAdapter sensorsAdapter;
|
||||
private StatisticsAdapter statisticsAdapter;
|
||||
private GridLayoutManager gridLayoutManager;
|
||||
private StatisticsDataModel viewModel;
|
||||
private LiveData<List<StatisticData>> statisticsLiveData;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private boolean preferenceMetricUnits;
|
||||
private boolean preferenceReportSpeed;
|
||||
private boolean preferenceShowAltitude;
|
||||
private boolean preferenceShowCoordinate;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
boolean updateUInecessary = false;
|
||||
@@ -69,19 +66,13 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && recordingData != null) {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_fields_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), recordingData.getTrackCategory());
|
||||
layout = PreferencesUtils.getCustomLayout(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_grade_altitude_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceShowAltitude = PreferencesUtils.isShowStatsAltitude(sharedPreferences, getContext());
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_coordinate_key, key)) {
|
||||
updateUInecessary = true;
|
||||
preferenceShowCoordinate = PreferencesUtils.isStatsShowCoordinate(sharedPreferences, getContext());
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_columns_key, key)) {
|
||||
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns(sharedPreferences, getContext()));
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
@@ -109,17 +100,28 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
|
||||
sensorsAdapter = new SensorsAdapter(getContext());
|
||||
statisticsAdapter = new StatisticsAdapter(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
||||
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(recordingData.getTrackCategory()));
|
||||
|
||||
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||
RecyclerView recyclerView = viewBinding.statsRecyclerView;
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
|
||||
final int numColumns = PreferencesUtils.getLayoutColumns(sharedPreferences, getContext());
|
||||
gridLayoutManager = new GridLayoutManager(getContext(), numColumns);
|
||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
if (statisticsAdapter.isItemWide(position)) {
|
||||
return numColumns;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
recyclerView.setLayoutManager(gridLayoutManager);
|
||||
recyclerView.setAdapter(statisticsAdapter);
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
@@ -131,13 +133,16 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
||||
|
||||
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
|
||||
statisticsLiveData = viewModel.getStatsData();
|
||||
statisticsLiveData.observe(getActivity(), statsDataList -> statisticsAdapter.swapData(statsDataList));
|
||||
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
@@ -158,160 +163,16 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
super.onDestroy();
|
||||
trackRecordingServiceConnection = null;
|
||||
sharedPreferences = null;
|
||||
viewModel = null;
|
||||
statisticsLiveData.removeObservers(getActivity());
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
if (isResumed()) {
|
||||
updateStats();
|
||||
setLocationValues();
|
||||
updateSensorDataUI();
|
||||
viewModel.update(recordingData, layout, preferenceMetricUnits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to fetch most recent {@link SensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
|
||||
* Also sets altitude gain and loss.
|
||||
*/
|
||||
private void updateSensorDataUI() {
|
||||
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
if (sensorDataSet != null) {
|
||||
List<SensorDataModel> sensorDataList = new ArrayList<>();
|
||||
if (sensorDataSet.getHeartRate() != null) {
|
||||
sensorDataList.add(new SensorDataModel(sensorDataSet.getHeartRate()));
|
||||
}
|
||||
if (sensorDataSet.getCyclingCadence() != null) {
|
||||
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingCadence()));
|
||||
}
|
||||
if (sensorDataSet.getCyclingPower() != null) {
|
||||
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingPower()));
|
||||
}
|
||||
sensorsAdapter.swapData(sensorDataList);
|
||||
}
|
||||
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getAltitudeParts(getContext(), recordingData.getTrackStatistics().getTotalAltitudeGain(), preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeGainValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeGainUnit.setText(parts.second);
|
||||
}
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getAltitudeParts(getContext(), recordingData.getTrackStatistics().getTotalAltitudeLoss(), preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeLossValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeLossUnit.setText(parts.second);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStats() {
|
||||
// Set activity type
|
||||
String trackIconValue = TrackIconUtils.getIconValue(getContext(), recordingData.getTrackCategory());
|
||||
viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue));
|
||||
|
||||
// Set time
|
||||
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getMovingTime()));
|
||||
updateTotalTime();
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
Speed speed = recordingData.getTrackStatistics().getAverageSpeed();
|
||||
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsAverageSpeedValue.setText(parts.first);
|
||||
viewBinding.statsAverageSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
Speed speed = recordingData.getTrackStatistics().getMaxSpeed();
|
||||
|
||||
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMaxSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMaxSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
Speed speed = recordingData.getTrackStatistics().getAverageMovingSpeed();
|
||||
|
||||
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMovingSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set altitude gain and loss
|
||||
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void updateTotalTime() {
|
||||
Duration totalTime = recordingData.getTrackStatistics().getTotalTime();
|
||||
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(totalTime));
|
||||
}
|
||||
|
||||
private void setLocationValues() {
|
||||
// Set speed/pace
|
||||
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
|
||||
|
||||
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null) {
|
||||
SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
|
||||
if (data.hasValue() && data.isRecent()) {
|
||||
speed = data.getValue().speed;
|
||||
}
|
||||
}
|
||||
|
||||
setSpeed(speed);
|
||||
|
||||
// Set distance
|
||||
{
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), recordingData.getTrackStatistics().getTotalDistance(), preferenceMetricUnits);
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
}
|
||||
// Set altitude
|
||||
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (preferenceShowAltitude) {
|
||||
// Current altitude
|
||||
Float altitude = null;
|
||||
int labelId = R.string.value_unknown;
|
||||
if (latestTrackPoint != null && latestTrackPoint.hasAltitude()) {
|
||||
altitude = (float) latestTrackPoint.getAltitude().toM();
|
||||
labelId = latestTrackPoint.getAltitude().getLabelId();
|
||||
}
|
||||
|
||||
Pair<String, String> parts = StringUtils.getAltitudeParts(getContext(), altitude, preferenceMetricUnits);
|
||||
viewBinding.statsAltitudeCurrentValue.setText(parts.first);
|
||||
viewBinding.statsAltitudeCurrentUnit.setText(parts.second);
|
||||
|
||||
viewBinding.statsAltitudeCurrentLabelEgm.setText(labelId);
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
viewBinding.statsCoordinateGroup.setVisibility(preferenceShowCoordinate ? View.VISIBLE : View.GONE);
|
||||
if (preferenceShowCoordinate) {
|
||||
String latitudeText = getContext().getString(R.string.value_unknown);
|
||||
String longitudeText = getContext().getString(R.string.value_unknown);
|
||||
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
|
||||
latitudeText = StringUtils.formatCoordinate(latestTrackPoint.getLatitude());
|
||||
longitudeText = StringUtils.formatCoordinate(latestTrackPoint.getLongitude());
|
||||
}
|
||||
viewBinding.statsLatitudeValue.setText(latitudeText);
|
||||
viewBinding.statsLongitudeValue.setText(longitudeText);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSpeed(Speed speed) {
|
||||
viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsSpeedValue.setText(parts.first);
|
||||
viewBinding.statsSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
private void onRecordingDataChanged(TrackRecordingService.RecordingData recordingData) {
|
||||
String oldCategory = this.recordingData.getTrackCategory();
|
||||
String newCategory = recordingData.getTrackCategory();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.dennisguse.opentracks.io.file;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -9,6 +10,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.exporter.GPXTrackExporter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.SharePictureTrackExporter;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
|
||||
/**
|
||||
@@ -85,6 +87,7 @@ public enum TrackFileFormat {
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
GPX {
|
||||
@Override
|
||||
public TrackExporter createTrackExporter(Context context) {
|
||||
@@ -99,6 +102,22 @@ public enum TrackFileFormat {
|
||||
public String getExtension() {
|
||||
return "gpx";
|
||||
}
|
||||
},
|
||||
|
||||
SHARE_PICTURE_PNG {
|
||||
@Override
|
||||
public TrackExporter createTrackExporter(Context context) {
|
||||
return new SharePictureTrackExporter(context, Bitmap.CompressFormat.PNG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMimeType() {
|
||||
return "image/png";
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
return "png";
|
||||
}
|
||||
};
|
||||
|
||||
private static final String MIME_KMZ = "application/vnd.google-earth.kmz";
|
||||
|
||||
@@ -37,6 +37,7 @@ import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.databinding.ExportActivityBinding;
|
||||
import de.dennisguse.opentracks.databinding.ToolbarBinding;
|
||||
import de.dennisguse.opentracks.io.file.ErrorListDialog;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
@@ -134,6 +135,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewBinding = ExportActivityBinding.inflate(getLayoutInflater());
|
||||
ToolbarBinding mergedBinding = ToolbarBinding.bind(viewBinding.getRoot());
|
||||
setContentView(viewBinding.getRoot());
|
||||
|
||||
directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
||||
@@ -144,7 +146,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
|
||||
String directoryDisplayName = FileUtils.getPath(documentFile);
|
||||
|
||||
viewBinding.toolbar.toolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName));
|
||||
mergedBinding.toolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName));
|
||||
|
||||
resultReceiver = new ExportServiceResultReceiver(new Handler(), this);
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
Instant startTime = tracks[0].getTrackStatistics().getStartTime();
|
||||
for (Track track : tracks) {
|
||||
Duration offset = Duration.between(track.getTrackStatistics().getStartTime(), startTime);
|
||||
writeLocations(track, offset);
|
||||
writeLocations(track);
|
||||
}
|
||||
if (hasMultipleTracks) {
|
||||
writeMultiTrackEnd();
|
||||
@@ -144,7 +144,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeLocations(Track track, Duration offset) throws InterruptedException {
|
||||
private void writeLocations(Track track) throws InterruptedException {
|
||||
boolean wroteTrack = false;
|
||||
boolean wroteSegment = false;
|
||||
|
||||
@@ -229,7 +229,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
printWriter.println("<atom:generator>" + StringUtils.formatCData(context.getString(R.string.app_name)) + "</atom:generator>");
|
||||
|
||||
writeTrackStyle();
|
||||
writePlacemarkerStyle(MARKER_STYLE, MARKER_ICON, 20, 2);
|
||||
writePlacemarkerStyle();
|
||||
printWriter.println("<Schema id=\"" + SCHEMA_ID + "\">");
|
||||
|
||||
writeSimpleArrayStyle(EXTENDED_DATA_TYPE_SPEED, context.getString(R.string.description_speed_ms));
|
||||
@@ -263,7 +263,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
float heading = getHeading(marker.getTrackId(), marker.getLocation());
|
||||
writePhotoOverlay(marker, heading);
|
||||
} else {
|
||||
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), MARKER_STYLE, marker.getLocation());
|
||||
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,16 +406,15 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
* @param name the name
|
||||
* @param category the category
|
||||
* @param description the description
|
||||
* @param styleName the style name
|
||||
* @param location the location
|
||||
*/
|
||||
private void writePlacemark(String name, String category, String description, String styleName, Location location) {
|
||||
private void writePlacemark(String name, String category, String description, Location location) {
|
||||
if (location != null) {
|
||||
printWriter.println("<Placemark>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
|
||||
printWriter.println("<description>" + StringUtils.formatCData(description) + "</description>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(location) + "</when></TimeStamp>");
|
||||
printWriter.println("<styleUrl>#" + styleName + "</styleUrl>");
|
||||
printWriter.println("<styleUrl>#" + KMLTrackExporter.MARKER_STYLE + "</styleUrl>");
|
||||
writeCategory(category);
|
||||
printWriter.println("<Point>");
|
||||
printWriter.println("<coordinates>" + getCoordinates(location, ",") + "</coordinates>");
|
||||
@@ -521,17 +520,12 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
|
||||
/**
|
||||
* Writes a placemarker style.
|
||||
*
|
||||
* @param name the name of the style
|
||||
* @param url the url of the style icon
|
||||
* @param x the x position of the hotspot
|
||||
* @param y the y position of the hotspot
|
||||
*/
|
||||
private void writePlacemarkerStyle(String name, String url, int x, int y) {
|
||||
printWriter.println("<Style id=\"" + name + "\"><IconStyle>");
|
||||
private void writePlacemarkerStyle() {
|
||||
printWriter.println("<Style id=\"" + KMLTrackExporter.MARKER_STYLE + "\"><IconStyle>");
|
||||
printWriter.println("<scale>1.3</scale>");
|
||||
printWriter.println("<Icon><href>" + url + "</href></Icon>");
|
||||
printWriter.println("<hotSpot x=\"" + x + "\" y=\"" + y + "\" xunits=\"pixels\" yunits=\"pixels\"/>");
|
||||
printWriter.println("<Icon><href>" + KMLTrackExporter.MARKER_ICON + "</href></Icon>");
|
||||
printWriter.println("<hotSpot x=\"" + 20 + "\" y=\"" + 2 + "\" xunits=\"pixels\" yunits=\"pixels\"/>");
|
||||
printWriter.println("</IconStyle></Style>");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.databinding.TrackSharePictureBinding;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Create a share picture for a track.
|
||||
* Here a layout is inflated and converted to PNG.
|
||||
* <p>
|
||||
* TODO
|
||||
* If this classes is used with non-visual context (e.g., ApplicationContext), it may not work in the future.
|
||||
* API31 introduces `ncorrectContextUseViolation`, which may thrown during runtime.
|
||||
* This code was tested on API30.
|
||||
* Some more information: https://stackoverflow.com/questions/68539083/inflate-layout-from-applicationcontext-i-e-in-a-contentprovider/68567186#68567186
|
||||
* <p>
|
||||
* Future plan: draw directly on a view.
|
||||
*/
|
||||
public class SharePictureTrackExporter implements TrackExporter {
|
||||
|
||||
private static final String TAG = SharePictureTrackExporter.class.getSimpleName();
|
||||
|
||||
private final static int imageWidth = 1024;
|
||||
|
||||
private final Context context;
|
||||
|
||||
private final Bitmap.CompressFormat compressFormat;
|
||||
|
||||
public SharePictureTrackExporter(Context context, Bitmap.CompressFormat compressFormat) {
|
||||
this.context = context;
|
||||
this.compressFormat = compressFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
if (tracks.length > 1) {
|
||||
Log.e(TAG, "Not yet implemented.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return writeTrack(tracks[0], outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
int imageHeight = imageWidth / 4 * 3;
|
||||
|
||||
TrackSharePictureBinding viewBinding = TrackSharePictureBinding.inflate(LayoutInflater.from(context));
|
||||
viewBinding.sharePictureCategory.setText(track.getCategory());
|
||||
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(PreferencesUtils.getSharedPreferences(context), context);
|
||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||
|
||||
viewBinding.sharePictureTotalTime.setText(StringUtils.formatElapsedTimeWithHour(track.getTrackStatistics().getTotalTime()));
|
||||
viewBinding.sharePictureTotalDistance.setText(StringUtils.formatDistance(context, track.getTrackStatistics().getTotalDistance(), metricUnits));
|
||||
viewBinding.sharePictureGain.setText(StringUtils.formatAltitude(context, trackStatistics.getTotalAltitudeGain(), metricUnits));
|
||||
|
||||
// prepare rendering
|
||||
View view = viewBinding.getRoot();
|
||||
int widthSpec = View.MeasureSpec.makeMeasureSpec(imageWidth, View.MeasureSpec.EXACTLY);
|
||||
int heightSpec = View.MeasureSpec.makeMeasureSpec(imageHeight, View.MeasureSpec.EXACTLY);
|
||||
view.measure(widthSpec, heightSpec);
|
||||
view.layout(0, 0, imageWidth, imageHeight);
|
||||
|
||||
// draw
|
||||
Canvas canvas = new Canvas();
|
||||
Bitmap bitmap = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
|
||||
canvas.setBitmap(bitmap);
|
||||
|
||||
view.draw(canvas);
|
||||
|
||||
// store
|
||||
return bitmap.compress(compressFormat, 100, outputStream);
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
Uri directoryUri = resultData.getData();
|
||||
|
||||
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, directoryUri);
|
||||
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), resultData, directoryUri);
|
||||
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri);
|
||||
break;
|
||||
case RESULT_CANCELED:
|
||||
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, null);
|
||||
|
||||
@@ -21,9 +21,9 @@ import android.util.Log;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -121,7 +121,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes) {
|
||||
switch (tag) {
|
||||
case TAG_MARKER:
|
||||
onMarkerStart(attributes);
|
||||
@@ -251,14 +251,14 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
|
||||
|
||||
private TrackPoint createTrackPoint() throws ParsingException {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||
|
||||
Instant parsedTime = null;
|
||||
try {
|
||||
trackPoint.setTime(StringUtils.parseTime(time));
|
||||
parsedTime = StringUtils.parseTime(time);
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, parsedTime);
|
||||
if (latitude == null || longitude == null) {
|
||||
return trackPoint;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
@@ -86,10 +85,6 @@ public class TrackImporter {
|
||||
this.trackPoints.addAll(trackPoints);
|
||||
}
|
||||
|
||||
void addMarker(Marker marker) {
|
||||
this.markers.add(marker);
|
||||
}
|
||||
|
||||
void addMarkers(List<Marker> markers) {
|
||||
this.markers.addAll(markers);
|
||||
}
|
||||
@@ -153,8 +148,7 @@ public class TrackImporter {
|
||||
updater.addTrackPoints(trackPoints, recordingDistanceInterval);
|
||||
track.setTrackStatistics(updater.getTrackStatistics());
|
||||
|
||||
Uri trackUri = contentProviderUtils.insertTrack(track);
|
||||
Track.Id trackId = new Track.Id(ContentUris.parseId(trackUri));
|
||||
Track.Id trackId = contentProviderUtils.insertTrack(track);
|
||||
|
||||
// Store TrackPoints
|
||||
adjustTrackPoints();
|
||||
|
||||
@@ -160,6 +160,7 @@ public class TrackDeleteService extends Service {
|
||||
return this.trackId != null && this.trackId.equals(trackId);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DeleteStatus{" +
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||
|
||||
class TrackRecordingManager {
|
||||
|
||||
private static final String TAG = TrackRecordingManager.class.getSimpleName();
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final Context context;
|
||||
|
||||
private Distance recordingDistanceInterval;
|
||||
private Distance maxRecordingDistance;
|
||||
|
||||
private Track track;
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||
|
||||
private TrackPoint lastTrackPoint;
|
||||
private TrackPoint lastValidTrackPoint;
|
||||
private boolean isIdle;
|
||||
|
||||
TrackRecordingManager(Context context) {
|
||||
this.context = context;
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
}
|
||||
|
||||
Track.Id start(TrackPoint segmentStartTrackPoint) {
|
||||
// Insert a track
|
||||
track = new Track();
|
||||
track.setId(contentProviderUtils.insertTrack(track));
|
||||
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
|
||||
insertTrackPoint(track, segmentStartTrackPoint);
|
||||
|
||||
//TODO Pass TrackPoint
|
||||
track.setName(TrackNameUtils.getTrackName(context, track.getId(), segmentStartTrackPoint.getTime()));
|
||||
|
||||
String category = PreferencesUtils.getDefaultActivity(PreferencesUtils.getSharedPreferences(context), context); //TODO Re-use sharedpreferences
|
||||
track.setCategory(category);
|
||||
track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
contentProviderUtils.updateTrack(track); //TODO Could we do this in the insert?
|
||||
|
||||
return track.getId();
|
||||
}
|
||||
|
||||
//TODO Handle non-existing trackId? Start a new track or exception?
|
||||
void resume(@NonNull Track.Id trackId, @NonNull TrackPoint segmentStartTrackPoint) {
|
||||
track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
||||
insertTrackPoint(track, segmentStartTrackPoint);
|
||||
}
|
||||
|
||||
void pause(TrackPointCreator handlerServer) {
|
||||
if (lastTrackPoint != null) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
}
|
||||
insertTrackPoint(track, handlerServer.createSegmentEnd());
|
||||
}
|
||||
|
||||
void end(TrackPointCreator handlerServer) {
|
||||
if (lastTrackPoint != null) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
}
|
||||
|
||||
TrackPoint segmentEnd = handlerServer.createSegmentEnd();
|
||||
insertTrackPoint(track, segmentEnd);
|
||||
|
||||
track = null;
|
||||
trackStatisticsUpdater = null;
|
||||
lastTrackPoint = null;
|
||||
lastValidTrackPoint = null;
|
||||
isIdle = false;
|
||||
}
|
||||
|
||||
Pair<Track, Pair<TrackPoint, SensorDataSet>> get(TrackPointCreator handlerServer) {
|
||||
if (handlerServer == null) {
|
||||
return null;
|
||||
}
|
||||
TrackStatisticsUpdater tmpTrackStatisticsUpdater = getTrackStatisticsUpdater();
|
||||
Pair<TrackPoint, SensorDataSet> current = handlerServer.createCurrentTrackPoint(lastTrackPoint);
|
||||
|
||||
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
|
||||
|
||||
Track track = getTrack(); //Get copy
|
||||
if (track == null) {
|
||||
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
|
||||
return null;
|
||||
}
|
||||
|
||||
track.setTrackStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
|
||||
|
||||
return new Pair<>(track, current);
|
||||
}
|
||||
|
||||
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
|
||||
if (name == null) {
|
||||
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(track.getId());
|
||||
if (nextMarkerNumber == null) {
|
||||
nextMarkerNumber = 1;
|
||||
}
|
||||
name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1);
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
if (trackPoint == null) {
|
||||
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
|
||||
return null;
|
||||
}
|
||||
|
||||
category = category != null ? category : "";
|
||||
description = description != null ? description : "";
|
||||
String icon = context.getString(R.string.marker_icon_url);
|
||||
photoUrl = photoUrl != null ? photoUrl : "";
|
||||
|
||||
|
||||
// Insert marker
|
||||
Marker marker = new Marker(name, description, category, icon, track.getId(), getTrackStatistics(), trackPoint, photoUrl);
|
||||
Uri uri = contentProviderUtils.insertMarker(marker);
|
||||
return new Marker.Id(ContentUris.parseId(uri));
|
||||
}
|
||||
|
||||
void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
|
||||
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
|
||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
|
||||
//Storing trackPoint
|
||||
|
||||
// Always insert the first segment location
|
||||
if (!currentSegmentHasTrackPoint()) {
|
||||
insertTrackPoint(track, trackPoint);
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
|
||||
if (distanceToLastTrackLocation != null) {
|
||||
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
insertTrackPoint(track, trackPoint);
|
||||
|
||||
isIdle = false;
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
|
||||
isIdle = false;
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isIdle && !trackPoint.isMoving()) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
|
||||
isIdle = true;
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isIdle && trackPoint.isMoving()) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
|
||||
isIdle = false;
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Not recording TrackPoint, idle");
|
||||
lastTrackPoint = trackPoint;
|
||||
}
|
||||
|
||||
Track getTrack() {
|
||||
return contentProviderUtils.getTrack(track.getId()); //Copy in memory; not via DB
|
||||
}
|
||||
|
||||
//Functionality that uses this method should happen here.
|
||||
@Deprecated
|
||||
TrackStatisticsUpdater getTrackStatisticsUpdater() {
|
||||
return new TrackStatisticsUpdater(trackStatisticsUpdater);
|
||||
}
|
||||
|
||||
TrackStatistics getTrackStatistics() {
|
||||
return trackStatisticsUpdater.getTrackStatistics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last valid track point in the current segment.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @return the location or null
|
||||
*/
|
||||
@Deprecated
|
||||
//Use lastValidTrackPoint
|
||||
private TrackPoint getLastValidTrackPointInCurrentSegment(Track.Id trackId) {
|
||||
if (!currentSegmentHasTrackPoint()) {
|
||||
return null;
|
||||
}
|
||||
return contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
}
|
||||
|
||||
private boolean currentSegmentHasTrackPoint() {
|
||||
return lastTrackPoint != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a trackPoint if this trackPoint is different than lastValidTrackPoint.
|
||||
*
|
||||
* @param track the track
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
private void insertTrackPointIfNewer(@NonNull Track track, @NonNull TrackPoint trackPoint) {
|
||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
if (lastValidTrackPoint != null && trackPoint.getTime().equals(lastValidTrackPoint.getTime())) {
|
||||
// Do not insert if inserted already
|
||||
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid track point time.");
|
||||
return;
|
||||
}
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a trackPoint.
|
||||
*
|
||||
* @param track the track
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
private void insertTrackPoint(@NonNull Track track, @NonNull TrackPoint trackPoint) {
|
||||
try {
|
||||
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
|
||||
contentProviderUtils.updateTrackStatistics(track.getId(), track.getTrackStatistics());
|
||||
} catch (SQLiteException e) {
|
||||
/*
|
||||
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
|
||||
* This is expected to happen extremely rarely (if our listener gets invoked twice at about the same time).
|
||||
*/
|
||||
Log.w(TAG, "SQLiteException", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSharedPreferenceChanged(@NonNull SharedPreferences sharedPreferences, String key) {
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
|
||||
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,17 +18,15 @@ package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
@@ -36,38 +34,30 @@ import androidx.core.app.TaskStackBuilder;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.content.data.Altitude;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
|
||||
import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager;
|
||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||
import de.dennisguse.opentracks.services.handlers.HandlerServer;
|
||||
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTask;
|
||||
import de.dennisguse.opentracks.services.tasks.PeriodicTask;
|
||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.EGM2008Utils;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.SystemUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||
|
||||
/**
|
||||
* A background service that registers a location listener and records track points.
|
||||
@@ -75,7 +65,7 @@ import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class TrackRecordingService extends Service implements HandlerServer.HandlerServerInterface, ExportServiceResultReceiver.Receiver {
|
||||
public class TrackRecordingService extends Service implements TrackPointCreator.Callback, ExportServiceResultReceiver.Receiver {
|
||||
|
||||
private static final String TAG = TrackRecordingService.class.getSimpleName();
|
||||
|
||||
@@ -86,10 +76,13 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
|
||||
|
||||
// The following variables are set in onCreate:
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
private PeriodicTaskExecutor voiceExecutor;
|
||||
private TrackRecordingServiceNotificationManager notificationManager;
|
||||
|
||||
private TrackRecordingManager trackRecordingManager;
|
||||
|
||||
private final EGM2008CorrectionManager egm2008CorrectionManager = new EGM2008CorrectionManager();
|
||||
|
||||
private Handler handler;
|
||||
private final Runnable updateRecordingData = new Runnable() {
|
||||
@Override
|
||||
@@ -107,8 +100,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private Distance recordingDistanceInterval;
|
||||
private Distance maxRecordingDistance;
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
@@ -121,31 +112,19 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(sharedPreferences, context));
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
|
||||
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context);
|
||||
}
|
||||
|
||||
handlerServer.onSharedPreferenceChanged(context, sharedPreferences, key);
|
||||
|
||||
handlerServer.onSharedPreferenceChanged(sharedPreferences, key);
|
||||
trackRecordingManager.onSharedPreferenceChanged(sharedPreferences, key);
|
||||
}
|
||||
};
|
||||
|
||||
// The following variables are set when recording:
|
||||
private WakeLock wakeLock;
|
||||
private BluetoothRemoteSensorManager remoteSensorManager;
|
||||
private AltitudeSumManager altitudeSumManager;
|
||||
|
||||
private EGM2008Utils.EGM2008Correction egm2008Correction;
|
||||
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||
private TrackPoint lastTrackPoint;
|
||||
private boolean isIdle;
|
||||
|
||||
private final Binder binder = new Binder();
|
||||
|
||||
private HandlerServer handlerServer;
|
||||
private TrackPointCreator handlerServer; //TODO Move to TrackRecordingManager?
|
||||
|
||||
private RecordingStatus recordingStatus;
|
||||
private MutableLiveData<RecordingStatus> recordingStatusObservable;
|
||||
@@ -163,9 +142,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
gpsStatusObservable = new MutableLiveData<>(STATUS_GPS_DEFAULT);
|
||||
recordingDataObservable = new MutableLiveData<>(NOT_RECORDING);
|
||||
|
||||
handlerServer = new HandlerServer(this);
|
||||
trackRecordingManager = new TrackRecordingManager(this);
|
||||
handlerServer = new TrackPointCreator(this);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTask.Factory());
|
||||
|
||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||
@@ -189,19 +168,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
public void onDestroy() {
|
||||
handler = null;
|
||||
|
||||
handlerServer.stop(this);
|
||||
handlerServer.stop();
|
||||
handlerServer = null;
|
||||
|
||||
if (remoteSensorManager != null) {
|
||||
remoteSensorManager.stop();
|
||||
remoteSensorManager = null;
|
||||
}
|
||||
|
||||
if (altitudeSumManager != null) {
|
||||
altitudeSumManager.stop(this);
|
||||
altitudeSumManager = null;
|
||||
}
|
||||
|
||||
// Reverse order from onCreate
|
||||
showNotification(false); //TODO Why?
|
||||
|
||||
@@ -214,8 +183,6 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
voiceExecutor = null;
|
||||
}
|
||||
|
||||
contentProviderUtils = null;
|
||||
|
||||
// This should be the next to last operation
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
|
||||
@@ -234,48 +201,16 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
return recordingStatus.isPaused();
|
||||
}
|
||||
|
||||
public Track.Id getRecordingTrackId() {
|
||||
return recordingStatus.getTrackId();
|
||||
}
|
||||
|
||||
//TODO
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
if (trackStatisticsUpdater == null) {
|
||||
return null;
|
||||
}
|
||||
return trackStatisticsUpdater.getTrackStatistics();
|
||||
}
|
||||
|
||||
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
|
||||
if (!isRecording() || isPaused()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(getRecordingTrackId());
|
||||
if (nextMarkerNumber == null) {
|
||||
nextMarkerNumber = 1;
|
||||
}
|
||||
name = getString(R.string.marker_name_format, nextMarkerNumber + 1);
|
||||
return trackRecordingManager.insertMarker(name, category, description, photoUrl);
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(getRecordingTrackId());
|
||||
if (trackPoint == null) {
|
||||
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
|
||||
return null;
|
||||
}
|
||||
|
||||
category = category != null ? category : "";
|
||||
description = description != null ? description : "";
|
||||
String icon = getString(R.string.marker_icon_url);
|
||||
photoUrl = photoUrl != null ? photoUrl : "";
|
||||
|
||||
TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics();
|
||||
|
||||
// Insert marker
|
||||
Marker marker = new Marker(name, description, category, icon, getRecordingTrackId(), stats, trackPoint, photoUrl);
|
||||
Uri uri = contentProviderUtils.insertMarker(marker);
|
||||
return new Marker.Id(ContentUris.parseId(uri));
|
||||
public void run(@NonNull PeriodicTask periodicTask) {
|
||||
periodicTask.run(recordingStatus.getTrackId(), trackRecordingManager.getTrackStatistics());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,30 +224,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
return null;
|
||||
}
|
||||
|
||||
// Insert a track
|
||||
Track track = new Track();
|
||||
Uri uri = contentProviderUtils.insertTrack(track);
|
||||
Track.Id trackId = new Track.Id(ContentUris.parseId(uri));
|
||||
|
||||
// Set recording status
|
||||
Track.Id trackId = trackRecordingManager.start(handlerServer.createSegmentStartManual());
|
||||
updateRecordingStatus(new RecordingStatus(trackId, false));
|
||||
|
||||
// Update database
|
||||
track.setId(trackId);
|
||||
|
||||
TrackPoint segmentStartTrackPoint = TrackPoint.createSegmentStartManual();
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
insertTrackPoint(track, segmentStartTrackPoint);
|
||||
|
||||
//TODO Pass TrackPoint
|
||||
track.setName(TrackNameUtils.getTrackName(this, trackId, segmentStartTrackPoint.getTime()));
|
||||
|
||||
String category = PreferencesUtils.getDefaultActivity(sharedPreferences, this);
|
||||
track.setCategory(category);
|
||||
track.setIcon(TrackIconUtils.getIconValue(this, category));
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
contentProviderUtils.updateTrack(track);
|
||||
|
||||
startRecording();
|
||||
return trackId;
|
||||
}
|
||||
@@ -324,17 +239,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
* @param trackId the id of the track to be resumed.
|
||||
*/
|
||||
public void resumeTrack(Track.Id trackId) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync the real time setting the stop time with current time.
|
||||
track.getTrackStatistics().setStopTime(Instant.now());
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
||||
|
||||
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
|
||||
handlerServer.resetSensorData();
|
||||
trackRecordingManager.resume(trackId, handlerServer.createSegmentStartManual());
|
||||
|
||||
// Set recording status
|
||||
updateRecordingStatus(new RecordingStatus(trackId, false));
|
||||
@@ -349,34 +255,16 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
return;
|
||||
}
|
||||
|
||||
// Set recording status
|
||||
updateRecordingStatus(new RecordingStatus(getRecordingTrackId(), false));
|
||||
|
||||
// Update database
|
||||
Track track = contentProviderUtils.getTrack(getRecordingTrackId());
|
||||
if (track != null) {
|
||||
insertTrackPoint(track, TrackPoint.createSegmentStartManual());
|
||||
}
|
||||
|
||||
startRecording();
|
||||
resumeTrack(recordingStatus.getTrackId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Common code for starting a new track, resuming a track, or restarting after phone reboot.
|
||||
* Common code for starting a new track or resuming a track.
|
||||
*/
|
||||
private void startRecording() {
|
||||
// Update instance variables
|
||||
remoteSensorManager = new BluetoothRemoteSensorManager(this);
|
||||
remoteSensorManager.start();
|
||||
|
||||
altitudeSumManager = new AltitudeSumManager();
|
||||
altitudeSumManager.start(this);
|
||||
|
||||
handler.postDelayed(updateRecordingData, RECORDING_DATA_UPDATE_INTERVAL.toMillis());
|
||||
|
||||
lastTrackPoint = null;
|
||||
isIdle = false;
|
||||
|
||||
startGps();
|
||||
|
||||
// Restore periodic tasks
|
||||
@@ -404,25 +292,16 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
|
||||
// Need to remember the recordingTrackId before setting it to null
|
||||
boolean wasPause = isPaused();
|
||||
Track.Id trackId = getRecordingTrackId();
|
||||
Track.Id trackId = recordingStatus.getTrackId();
|
||||
|
||||
// Set recording status
|
||||
updateRecordingStatus(STATUS_DEFAULT);
|
||||
|
||||
if (!wasPause) {
|
||||
// Update database
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
if (track != null) {
|
||||
if (lastTrackPoint != null) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
trackRecordingManager.end(handlerServer);
|
||||
}
|
||||
|
||||
insertTrackPoint(track, TrackPoint.createSegmentEnd());
|
||||
}
|
||||
}
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
ExportUtils.postWorkoutExport(this, track, new ExportServiceResultReceiver(new Handler(), this));
|
||||
ExportUtils.postWorkoutExport(this, trackId, new ExportServiceResultReceiver(new Handler(), this));
|
||||
|
||||
endRecording(true);
|
||||
}
|
||||
@@ -435,16 +314,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
}
|
||||
|
||||
// Set recording status
|
||||
updateRecordingStatus(new RecordingStatus(getRecordingTrackId(), true));
|
||||
updateRecordingStatus(new RecordingStatus(recordingStatus.getTrackId(), true));
|
||||
|
||||
// Update database
|
||||
Track track = contentProviderUtils.getTrack(getRecordingTrackId());
|
||||
if (track != null) {
|
||||
if (lastTrackPoint != null) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
}
|
||||
insertTrackPoint(track, TrackPoint.createSegmentEnd());
|
||||
}
|
||||
trackRecordingManager.pause(handlerServer);
|
||||
|
||||
endRecording(false);
|
||||
|
||||
@@ -468,18 +340,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
voiceExecutor.shutdown();
|
||||
|
||||
// Update instance variables
|
||||
if (remoteSensorManager != null) {
|
||||
remoteSensorManager.stop();
|
||||
remoteSensorManager = null;
|
||||
}
|
||||
if (altitudeSumManager != null) {
|
||||
altitudeSumManager.stop(this);
|
||||
altitudeSumManager = null;
|
||||
}
|
||||
|
||||
lastTrackPoint = null;
|
||||
|
||||
handlerServer.stop(this);
|
||||
handlerServer.stop();
|
||||
|
||||
stopGps(trackStopped);
|
||||
}
|
||||
@@ -496,7 +357,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
void stopGps(boolean shutdown) {
|
||||
if (!isRecording()) return;
|
||||
|
||||
handlerServer.stop(this);
|
||||
handlerServer.stop();
|
||||
showNotification(false);
|
||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||
if (shutdown) {
|
||||
@@ -504,120 +365,16 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last valid track point in the current segment.
|
||||
*
|
||||
* @param trackId the track id
|
||||
* @return the location or null
|
||||
*/
|
||||
private TrackPoint getLastValidTrackPointInCurrentSegment(Track.Id trackId) {
|
||||
if (!currentSegmentHasTrackPoint()) {
|
||||
return null;
|
||||
}
|
||||
return contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
}
|
||||
|
||||
private boolean currentSegmentHasTrackPoint() {
|
||||
return lastTrackPoint != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newTrackPoint(TrackPoint trackPoint, int recordingGpsAccuracy) {
|
||||
public void newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
|
||||
if (!isRecording() || isPaused()) {
|
||||
Log.w(TAG, "Ignore newTrackPoint. Not recording or paused.");
|
||||
return;
|
||||
}
|
||||
|
||||
Track track = contentProviderUtils.getTrack(getRecordingTrackId());
|
||||
if (track == null) {
|
||||
Log.w(TAG, "Ignore newTrackPoint. No track.");
|
||||
return;
|
||||
}
|
||||
|
||||
remoteSensorManager.fill(trackPoint);
|
||||
|
||||
correctAltitude(trackPoint);
|
||||
|
||||
notificationManager.updateTrackPoint(this, track.getTrackStatistics(), trackPoint, recordingGpsAccuracy);
|
||||
|
||||
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
|
||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
|
||||
//Storing trackPoint
|
||||
|
||||
// Always insert the first segment location
|
||||
if (!currentSegmentHasTrackPoint()) {
|
||||
insertTrackPoint(track, trackPoint);
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
|
||||
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
insertTrackPoint(track, trackPoint);
|
||||
|
||||
isIdle = false;
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
|
||||
isIdle = false;
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isIdle && !trackPoint.isMoving()) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
|
||||
isIdle = true;
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isIdle && trackPoint.isMoving()) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
|
||||
isIdle = false;
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Not recording TrackPoint, idle");
|
||||
lastTrackPoint = trackPoint;
|
||||
}
|
||||
|
||||
//TODO Identical to TrackDataHub
|
||||
private void correctAltitude(TrackPoint trackPoint) {
|
||||
if (!trackPoint.hasLocation() || !trackPoint.hasAltitude()) {
|
||||
Log.d(TAG, "No altitude correction necessary.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (egm2008Correction == null || !egm2008Correction.canCorrect(trackPoint.getLocation())) {
|
||||
try {
|
||||
egm2008Correction = EGM2008Utils.createCorrection(this, trackPoint.getLocation());
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Could not load altitude correction for " + trackPoint, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
trackPoint.setAltitude(Altitude.EGM2008.of(egm2008Correction.correctAltitude(trackPoint.getLocation())));
|
||||
trackRecordingManager.onNewTrackPoint(trackPoint, thresholdHorizontalAccuracy);
|
||||
notificationManager.updateTrackPoint(this, trackRecordingManager.getTrackStatistics(), trackPoint, thresholdHorizontalAccuracy);
|
||||
voiceExecutor.update(recordingStatus.getTrackId(), trackRecordingManager.getTrackStatistics());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -630,58 +387,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a trackPoint if this trackPoint is different than lastValidTrackPoint.
|
||||
*
|
||||
* @param track the track
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
private void insertTrackPointIfNewer(@NonNull Track track, @NonNull TrackPoint trackPoint) {
|
||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
if (lastValidTrackPoint != null && trackPoint.getTime().equals(lastValidTrackPoint.getTime())) {
|
||||
// Do not insert if inserted already
|
||||
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid track point time.");
|
||||
return;
|
||||
}
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a trackPoint.
|
||||
*
|
||||
* @param track the track
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
private void insertTrackPoint(@NonNull Track track, @NonNull TrackPoint trackPoint) {
|
||||
try {
|
||||
if (!TrackPoint.Type.SEGMENT_START_MANUAL.equals(trackPoint.getType())) {
|
||||
altitudeSumManager.fill(trackPoint);
|
||||
altitudeSumManager.reset();
|
||||
|
||||
remoteSensorManager.fill(trackPoint);
|
||||
remoteSensorManager.reset();
|
||||
}
|
||||
|
||||
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||
|
||||
contentProviderUtils.updateTrack(track);
|
||||
} catch (SQLiteException e) {
|
||||
/*
|
||||
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
|
||||
* This is expected to happen extremely rarely (if our listener gets invoked twice at about the same time).
|
||||
*/
|
||||
Log.w(TAG, "SQLiteException", e);
|
||||
}
|
||||
voiceExecutor.update();
|
||||
}
|
||||
|
||||
private void showNotification(boolean isGpsStarted) {
|
||||
if (isRecording()) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, getRecordingTrackId());
|
||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingStatus.getTrackId());
|
||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||
.addParentStack(TrackRecordingActivity.class)
|
||||
.addNextIntent(intent)
|
||||
@@ -707,18 +416,14 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
}
|
||||
}
|
||||
|
||||
// This is used to modify the state of this service while testing; must be called after startNewTrack().
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public void setRemoteSensorManager(@NonNull BluetoothRemoteSensorManager remoteSensorManager) {
|
||||
this.remoteSensorManager = remoteSensorManager;
|
||||
public Duration getTotalTime() {
|
||||
return trackRecordingManager.getTrackStatistics().getTotalTime();
|
||||
}
|
||||
|
||||
// This is used to modify the state of this service while testing; must be called after startNewTrack().
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public void setAltitudeSumManager(@NonNull AltitudeSumManager altitudeSumManager) {
|
||||
this.altitudeSumManager = altitudeSumManager;
|
||||
public TrackPointCreator getHandlerServer() {
|
||||
return handlerServer;
|
||||
}
|
||||
|
||||
public LiveData<GpsStatusValue> getGpsStatusObservable() {
|
||||
@@ -734,35 +439,25 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
Log.w(TAG, "Currently not recording; cannot update data.");
|
||||
return;
|
||||
}
|
||||
Track track = contentProviderUtils.getTrack(recordingStatus.getTrackId());
|
||||
|
||||
// Compute temporary track statistics using sensorData and update time.
|
||||
//TODO This somehow should happen in the HandlerServer as we create a new TrackPoint.
|
||||
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater);
|
||||
TrackPoint tmpLastTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT);
|
||||
|
||||
if (lastTrackPoint != null && lastTrackPoint.hasLocation()) {
|
||||
//TODO Should happen in TrackPoint? via constructor
|
||||
tmpLastTrackPoint.setSpeed(lastTrackPoint.getSpeed());
|
||||
tmpLastTrackPoint.setAltitude(lastTrackPoint.getAltitude());
|
||||
tmpLastTrackPoint.setLongitude(lastTrackPoint.getLongitude());
|
||||
tmpLastTrackPoint.setLatitude(lastTrackPoint.getLatitude());
|
||||
}
|
||||
|
||||
BluetoothRemoteSensorManager localRemoteSensorManager = this.remoteSensorManager;
|
||||
AltitudeSumManager localAltitudeSumManager = this.altitudeSumManager;
|
||||
if (localAltitudeSumManager == null || localRemoteSensorManager == null) {
|
||||
TrackPointCreator localHandlerServer = this.handlerServer;
|
||||
if (localHandlerServer == null) {
|
||||
// when this happens, no recording is running and we should not send any notifications.
|
||||
//TODO This implementation is not a good idea; rather solve the issue for this properly
|
||||
return;
|
||||
}
|
||||
localAltitudeSumManager.fill(tmpLastTrackPoint);
|
||||
SensorDataSet sensorDataSet = localRemoteSensorManager.getSensorDataSet();
|
||||
sensorDataSet.fillTrackPoint(tmpLastTrackPoint);
|
||||
tmpTrackStatisticsUpdater.addTrackPoint(tmpLastTrackPoint, recordingDistanceInterval);
|
||||
track.setTrackStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
|
||||
|
||||
recordingDataObservable.postValue(new RecordingData(track, tmpLastTrackPoint, sensorDataSet));
|
||||
Pair<Track, Pair<TrackPoint, SensorDataSet>> data = trackRecordingManager.get(handlerServer);
|
||||
if (data == null) {
|
||||
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
|
||||
return;
|
||||
}
|
||||
TrackPoint trackPoint = data.second.first;
|
||||
egm2008CorrectionManager.correctAltitude(this, trackPoint);
|
||||
|
||||
recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second));
|
||||
}
|
||||
|
||||
public LiveData<RecordingStatus> getRecordingStatusObservable() {
|
||||
|
||||