Compare commits
161 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d6da5f0862 | |||
| c3d55127fe | |||
| b1032f898a | |||
| d4a3762641 | |||
| ce5eec6a68 | |||
| cf71829bec | |||
| 39606ccd9f | |||
| 9dd67e4db7 | |||
| 212345dea1 | |||
| 08ba7d50c7 | |||
| 62401b2c5c | |||
| 7cfdacecc3 | |||
| 8f2e234c63 | |||
| dcd101a20a | |||
| cb5f3ace5a | |||
| 8e889fbce2 | |||
| ec390c4a81 | |||
| 30e9e930ab | |||
| 3719d9ac8f | |||
| e33cfa8666 | |||
| 8ff1db6a23 | |||
| a9228017fc | |||
| 41e5e71804 | |||
| 9f858168f8 | |||
| cfdcea2d88 | |||
| 2d2db41c5d | |||
| 2c73d0645a | |||
| ed1027eccc | |||
| 0dec17eba5 | |||
| 774d701a09 | |||
| 23bcbf09a5 | |||
| 115966cbdf | |||
| 1862bcc6f4 | |||
| 900910e9f7 | |||
| 45571c68c9 | |||
| 083b620e41 | |||
| 2f7a56bb17 | |||
| d8988663cb | |||
| c742dae6e0 | |||
| 292437e6bd | |||
| 8e9cde7222 | |||
| d07ab3cc22 | |||
| 90493ae498 | |||
| f6b994572a | |||
| 24826ff442 | |||
| d97e5ab1ef | |||
| 52a110d998 | |||
| 12e18b0bcd | |||
| 66dac4b0ca | |||
| 1bb8cbb82a | |||
| 2f5b7b63e4 | |||
| 58ca78b976 | |||
| 8079a25a42 | |||
| 0527e4ab14 | |||
| b6b4c5aa39 | |||
| 8c5c9d7947 | |||
| eac3d4b3db | |||
| d7bc550b29 | |||
| d76d5673a8 | |||
| 847be686ea | |||
| 1e3445cfa2 | |||
| d0f7939f1a | |||
| 000b8149f8 | |||
| 05ffa5a373 | |||
| e92f1107aa | |||
| 2ad1fab670 | |||
| 8c04129ef9 | |||
| 400d9fe948 | |||
| 5d79510750 | |||
| 18bb64580f | |||
| 7adebfc0d1 | |||
| 2178ff97d3 | |||
| f070495fb4 | |||
| d99fb5ea47 | |||
| d69a726e0e | |||
| e0111c2ffd | |||
| 487bd6c3a0 | |||
| 40aab9444a | |||
| 82b460b57b | |||
| 68baad16e2 | |||
| 26ed9b24d3 | |||
| 9f6089e7f1 | |||
| d0eef44bf8 | |||
| eead3b5721 | |||
| b8f0c59198 | |||
| 8f408dceba | |||
| d1dfaa5443 | |||
| b2da849e25 | |||
| 88579be0a2 | |||
| 70d971d8fd | |||
| 70b0dac4b6 | |||
| 038e8d3e06 | |||
| 932bb5ed5a | |||
| 8f2d1722ad | |||
| 9cb3d8c1bb | |||
| fa02ba9c69 | |||
| 4f31607e3e | |||
| b5165de7f0 | |||
| b32e87e10b | |||
| 29a1daa177 | |||
| d5cfd118f6 | |||
| 5981340bb9 | |||
| f562d51d9a | |||
| d77b9243af | |||
| 2c7e665206 | |||
| 1aa6b94f19 | |||
| 68c447f77e | |||
| 6b3a2eae5e | |||
| 3869dba72b | |||
| 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 |
@@ -0,0 +1,34 @@
|
|||||||
|
#https://github.com/marketplace/actions/android-emulator-runner
|
||||||
|
name: Build nightly
|
||||||
|
on: [ push ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nightly-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
distribution: 'adopt'
|
||||||
|
java-version: '11'
|
||||||
|
|
||||||
|
- name: Assemble nighlty APK
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo ${NIGHTLY_STORE_FILE} | base64 -d > KEY_NIGHTLY.jks
|
||||||
|
./gradlew assembleNightly -Dnightly_store_file=KEY_NIGHTLY.jks -Dnightly_store_password="${NIGHTLY_STORE_PASSWORD}" -Dnightly_key_alias="${NIGHTLY_KEY_ALIAS}" -Dnightly_key_password="${NIGHTLY_STORE_PASSWORD}"
|
||||||
|
env:
|
||||||
|
NIGHTLY_STORE_FILE: ${{ secrets.NIGHTLY_STORE_FILE }}
|
||||||
|
NIGHTLY_STORE_PASSWORD: ${{ secrets.NIGHTLY_STORE_PASSWORD }}
|
||||||
|
NIGHTLY_KEY_ALIAS: ${{ secrets.NIGHTLY_KEY_ALIAS }}
|
||||||
|
|
||||||
|
- name: Archive
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: OpenTracks-nightly.apk
|
||||||
|
path: ./build/outputs/apk/nightly/*.apk
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
@@ -9,11 +9,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
- uses: actions/setup-java@v2
|
- uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
distribution: 'adopt'
|
distribution: 'zulu'
|
||||||
java-version: '11'
|
java-version: '11'
|
||||||
|
|
||||||
- run: ./gradlew lint
|
- run: ./gradlew lint
|
||||||
@@ -11,15 +11,15 @@ jobs:
|
|||||||
target: [ default ]
|
target: [ default ]
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v2.4.0
|
||||||
|
|
||||||
- uses: actions/setup-java@v2
|
- uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
distribution: 'adopt'
|
distribution: 'zulu'
|
||||||
java-version: '11'
|
java-version: '11'
|
||||||
|
|
||||||
- name: run tests
|
- name: run tests
|
||||||
uses: reactivecircus/android-emulator-runner@v2.19.1
|
uses: reactivecircus/android-emulator-runner@v2.21.0
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|||||||
@@ -47,20 +47,29 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
|||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://www.amazon.de/dp/B09K2KNQ7X/">
|
||||||
|
<img alt="Get it on Amazon App Store" src="https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/devportal2/res/images/amazon-appstore-badge-english-white.png" height="40" align="middle">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
<div>
|
<div>
|
||||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot1.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png">
|
||||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot2.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png">
|
||||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot3.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/images/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/screenshot4.png">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot5.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot5.png">
|
||||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot6.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot6.png">
|
||||||
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot7.png">
|
<img width="23%" src="fastlane/metadata/android/en-US/images/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/screenshot8.png">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ Requirement: `gem install fastlane`
|
|||||||
(requires keystore)
|
(requires keystore)
|
||||||
2. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
|
2. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
|
||||||
3. Update store meta data (e.g., description and screenshots)
|
3. Update store meta data (e.g., description and screenshots)
|
||||||
`fastlane supply --version_code VERSION_CODE --skip_upload_apk`
|
`fastlane metadata`
|
||||||
(use the version code from the manifest)
|
(requires API key configured in `Appfile`)
|
||||||
(requires API key)
|
|
||||||
|
Supported languages: https://support.google.com/googleplay/android-developer/table/4419860
|
||||||
|
|
||||||
## Translations (localization)
|
## Translations (localization)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Overview of tested Bluetooth LE sensors
|
# Overview of tested Bluetooth LE sensors
|
||||||
|
|
||||||
## HRS: Heartrate Service
|
## 0x180D: Heartrate Service
|
||||||
|
|
||||||
* Decathlon|Geonaute|Kalenji Dual HRM Belt
|
* Decathlon|Geonaute|Kalenji Dual HRM Belt
|
||||||
* moofit heart rate monitor
|
* moofit heart rate monitor
|
||||||
@@ -11,8 +11,9 @@
|
|||||||
* Wahoo Tickr (Model: WFBTHR02)
|
* Wahoo Tickr (Model: WFBTHR02)
|
||||||
* Mi Band 3, Amazfit Band 5, Amazfit Bip. These devices are manufactured by Huami, and other Huami devices will very likely work. Theoretically, this sensor must be enabled, but testing reveals that it works even without enabling. For details on how to enable see [Gadgetbridge wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Huami-Heartrate-measurement#bluetooth-heart-rate-sensor)
|
* Mi Band 3, Amazfit Band 5, Amazfit Bip. These devices are manufactured by Huami, and other Huami devices will very likely work. Theoretically, this sensor must be enabled, but testing reveals that it works even without enabling. For details on how to enable see [Gadgetbridge wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Huami-Heartrate-measurement#bluetooth-heart-rate-sensor)
|
||||||
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
|
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
|
||||||
|
* Garmin HRM-Dual (Reference: 010-12883-00)
|
||||||
|
|
||||||
## CSCP: Cycling Cadence and Speed Service
|
## 0x1816: Cycling Cadence and Speed Service
|
||||||
|
|
||||||
Please note that the according to the specification these sensors _may_ provide speed as well as cadence.
|
Please note that the according to the specification these sensors _may_ provide speed as well as cadence.
|
||||||
However, often only one value is provided.
|
However, often only one value is provided.
|
||||||
@@ -35,7 +36,7 @@ However, often only one value is provided.
|
|||||||
This sensor reports cadence data as speed.
|
This sensor reports cadence data as speed.
|
||||||
A workaround is in place.
|
A workaround is in place.
|
||||||
|
|
||||||
## Cycling Power Measurement
|
## 0x1818: Power Measurement
|
||||||
|
|
||||||
We only support _Instantaneous Power_.
|
We only support _Instantaneous Power_.
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ _WARNING_: we only support physically movements with valid GPS coordinates.
|
|||||||
Using a device indoors (no GPS available) will only show the current power values, but won't store anything.
|
Using a device indoors (no GPS available) will only show the current power values, but won't store anything.
|
||||||
Also the distance is not computed.
|
Also the distance is not computed.
|
||||||
|
|
||||||
|
For cycling:
|
||||||
* Assioma Duo Powermeter
|
* Assioma Duo Powermeter
|
||||||
* Elite drivo II
|
* Elite drivo II
|
||||||
* QUARQ Red DZero Powermeter
|
* QUARQ Red DZero Powermeter
|
||||||
@@ -50,8 +52,28 @@ Also the distance is not computed.
|
|||||||
* Wahoo Kickr v4.0
|
* Wahoo Kickr v4.0
|
||||||
* Rotor 2INpower DM Road (only supports power measurement, cadence measurement is propietary @ firmware v1.061)
|
* Rotor 2INpower DM Road (only supports power measurement, cadence measurement is propietary @ firmware v1.061)
|
||||||
|
|
||||||
## RSC: running cadence and speed
|
For running:
|
||||||
|
* Stryd
|
||||||
|
|
||||||
|
## 0x1814: running cadence and speed
|
||||||
|
|
||||||
We do not support type of movement (i.e., walking vs running).
|
We do not support type of movement (i.e., walking vs running).
|
||||||
|
|
||||||
* Polar Stride
|
* Polar Stride
|
||||||
|
* Stryd (incl. power)
|
||||||
|
|
||||||
|
## Tested Barometric Sensor Smartphones
|
||||||
|
|
||||||
|
There are smartphones that have a barometric sensor that OpenTracks uses to compute altitude gain and loss.
|
||||||
|
|
||||||
|
In our tests there are smartphones whose barometric sensor works well and others don't. Here is a list of the smartphones tested:
|
||||||
|
|
||||||
|
|Brand|Model|Android Version|Tests|
|
||||||
|
|-----|-----|---------------|-----------|
|
||||||
|
|Google|Pixel|LineageOS 17.1|Ok|
|
||||||
|
|Google|Pixel 2 XL|LineageOS 17.1|Ok on the bike handlebars but it fails when I carry it in a pocket or backpack|
|
||||||
|
|Google|Pixel 3|Android 11|Failure|
|
||||||
|
|Google|Pixel 3a|Android 11|Ok|
|
||||||
|
|Google|Pixel 3a|Lineage 18.1|Ok|
|
||||||
|
|Google|Pixel 4a|Android 11|Ok|
|
||||||
|
|Sony|Xperia XZ1 Compact|LineageOS 16.1|Failure|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.0.0'
|
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +30,12 @@ def getVersionName = { ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def versionCodeFromDate = { ->
|
||||||
|
def date = new Date()
|
||||||
|
def formattedDate = date.format('yyyyMMdd')
|
||||||
|
return Integer.valueOf(formattedDate)
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileOptions {
|
compileOptions {
|
||||||
coreLibraryDesugaringEnabled true
|
coreLibraryDesugaringEnabled true
|
||||||
@@ -41,7 +47,7 @@ android {
|
|||||||
compileSdkVersion 31
|
compileSdkVersion 31
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding true
|
||||||
}
|
}
|
||||||
|
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
@@ -50,8 +56,8 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
versionCode 4358
|
versionCode 4519
|
||||||
versionName "v3.19.0"
|
versionName "v3.22.0"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
@@ -59,9 +65,19 @@ android {
|
|||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 31
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
}
|
}
|
||||||
|
signingConfigs {
|
||||||
|
nightly {
|
||||||
|
if (System.getProperty("nightly_store_file") != null) {
|
||||||
|
storeFile file(System.getProperty("nightly_store_file"))
|
||||||
|
storePassword System.getProperty("nightly_store_password")
|
||||||
|
keyAlias System.getProperty("nightly_key_alias")
|
||||||
|
keyPassword System.getProperty("nightly_key_password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
@@ -69,6 +85,11 @@ android {
|
|||||||
versionNameSuffix "-debug"
|
versionNameSuffix "-debug"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nightly {
|
||||||
|
signingConfig signingConfigs.nightly
|
||||||
|
applicationIdSuffix ".nightly"
|
||||||
|
}
|
||||||
|
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
}
|
}
|
||||||
@@ -81,6 +102,14 @@ android {
|
|||||||
|
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
variant.resValue "string", "applicationId", variant.applicationId
|
variant.resValue "string", "applicationId", variant.applicationId
|
||||||
|
|
||||||
|
if (variant.buildType.name == 'nightly') {
|
||||||
|
variant.outputs.all {
|
||||||
|
setVersionCodeOverride(versionCodeFromDate())
|
||||||
|
setVersionNameOverride(getVersionName())
|
||||||
|
outputFileName = "${applicationId}_${variant.versionCode}.apk"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +127,7 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
androidTestImplementation 'org.mockito:mockito-android:3.10.0'
|
androidTestImplementation 'org.mockito:mockito-android:4.0.0'
|
||||||
|
|
||||||
androidTestUtil 'androidx.test:orchestrator:1.4.0'
|
androidTestUtil 'androidx.test:orchestrator:1.4.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ class TrackPoint {
|
|||||||
- sensor_power
|
- sensor_power
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Marker {
|
class Marker {
|
||||||
- id
|
- id
|
||||||
- trackId
|
- trackId
|
||||||
@@ -58,6 +57,26 @@ class Marker {
|
|||||||
- photoUrl
|
- 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
|
note left of TrackPoint
|
||||||
As of OpenTracks version 3.15.0, all times are using device time.
|
As of OpenTracks version 3.15.0, all times are using device time.
|
||||||
Before that TrackPoint.time contained GPS time (determined by GPS hardware).
|
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)
|
default_platform(:android)
|
||||||
|
|
||||||
platform :android do
|
platform :android do
|
||||||
desc "Runs all the tests"
|
desc "Upload metadata"
|
||||||
lane :test do
|
lane :metadata do
|
||||||
gradle(task: "test")
|
upload_to_play_store(
|
||||||
end
|
skip_upload_apk: true,
|
||||||
|
skip_upload_changelogs: true
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
v3.20.1: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Announcements can now be used by both distance and time
|
||||||
|
- Announcements beep if no Text-to-speech software is installed
|
||||||
|
- Removed adaptive GPS location frequency
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v3.20.2: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Elevation gain and loss are integers instead of floats
|
||||||
|
- Ask for permission before using Bluetooth by a Toast message
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Set FLAG_IMMUTABLE for PendingIntents to avoid crash on Android 12
|
||||||
|
- Cadence sensor returns 0 instead of None when user is not pedaling
|
||||||
|
- Speed sensor returns 0 instead of the last speed when it's stopped
|
||||||
|
- With sensors, moving time stops when is not moving
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
v3.21.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- While recording: remove idle/non-idle creation of TrackPoints
|
||||||
|
- Settings are now in separate folders
|
||||||
|
- Opening OpenTracks from notification (while recording) and pressing back opens track list
|
||||||
|
- GPX compatibilty: support <speed> for import
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Altitude and distance (from BlE sensor): only reset counters if data was actually stored
|
||||||
|
- Import without speed, requires computing speed
|
||||||
|
- BLE running sensor not connected, should not be reported as present
|
||||||
|
- BLE running sensor: show data while recording
|
||||||
|
- Activity type icon was not updated while changing activity
|
||||||
|
|
||||||
|
Developer:
|
||||||
|
- SharedPrefences via static reference
|
||||||
|
- Tests are executed against in-memory database
|
||||||
|
- TrackRecordingManager keeps last TrackPoint in memory
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
v3.22.0: OpenTracks
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Locations are only stored if traveled distance > recording distance interval
|
||||||
|
(removed left over functionality from idle/non-idle detection)
|
||||||
|
- UI can be configured to show a clock
|
||||||
|
- Idle speed is now configurable
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Intervals: crash when gain but not loss was stored
|
||||||
|
- While recording: prevent crash when disabling voice announcements
|
||||||
|
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 aplikaĵo por registri vian sportan aktivecon, kiu plene respektas vian privatecon.
|
OpenTracks estas aplikaĵo por registri vian sportan aktivecon, kiu plene respektas vian privatecon.
|
||||||
|
|
||||||
Eblaĵoj:
|
Ebloj:
|
||||||
• registri vian sportan kaj alian aktivecon eksterdome
|
• registri vian sportan kaj alian aktivecon eksterdome
|
||||||
• fotoj kaj markoj: marki interesajn lokojn dum registri aktivecon
|
• fotoj kaj markoj: marki interesajn lokojn dum registri aktivecon
|
||||||
• registri ŝanĝon de altitudo uzante barometron
|
• registri ŝanĝon de altitudo uzante barometron
|
||||||
• montri altitudon en EGM2008 ()super averaĝa marnivelo); elporti ilin kiel WGS84
|
• montri altitudon en EGM2008 (super averaĝa marnivelo); elporti ilin kiel WGS84
|
||||||
• voĉaj anoncoj
|
• voĉaj anoncoj
|
||||||
• subteno por sentiloj BLE por registri vian pulson; rapidon, veturigitan distancon, pedal-rapidon, povumon (biciklado)
|
• 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
|
• elporti datumojn al dosieroj KMZ (kun fotoj), KML aŭ GPX
|
||||||
• funkcias malkonekte: interreta permeso ne estas uzata
|
• funkcias malkonekte: interreta permeso ne estas uzata
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
OpenTracks er en app for opptak av sportsaktivitet som respekterer ditt personvern.
|
||||||
|
|
||||||
Funksjoner:
|
Funksjoner:
|
||||||
* Sportssoring: Spor dine sports- og utendørsaktiviteter
|
* Sportsporing: Registrer dine sports- og utendørsaktiviteter
|
||||||
* Bilder og markører: Marker interessante steder under sporing
|
* Bilder og markører: Marker interessante steder under sporing
|
||||||
* Høydeendringer: via barometer
|
* Total stigning og nedstigning: via barometersensor
|
||||||
* Høyde vist i EGM2008 (over havnivå); eksportert som WGS84
|
* Høyde vist i EGM2008 (over havnivå); eksportert som WGS84
|
||||||
* Stemmekunngjøringer
|
* Talemeldinger
|
||||||
* Støtter Blåtann LE-sensorer: pulse, hastighet og distanse (sykling), takt (sykling), og effektmeter (sykling)
|
* 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
|
* Eksport av data: Enten som KMZ (inkl. bilder), KML eller GPX
|
||||||
* Ingen tilgang til Internett: Ingenting lastes opp noensteds hen
|
* Ingen internettilgang: Internett brukes ikke
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
En sportsregistrerer som respekterer ditt personvern.
|
En opptaker av sportsaktivitet som respekterer ditt personvern.
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
package de.dennisguse.opentracks;
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<>() {
|
|
||||||
@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,136 @@
|
|||||||
|
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.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.filters.LargeTest;
|
||||||
|
import androidx.test.rule.ActivityTestRule;
|
||||||
|
import androidx.test.rule.GrantPermissionRule;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
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.settings.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.
|
||||||
|
Layout layout = PreferencesUtils.getCustomLayout();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -143,7 +143,7 @@ public class EspressoDeleteTrackTest {
|
|||||||
private static Matcher<View> childAtPosition(
|
private static Matcher<View> childAtPosition(
|
||||||
final Matcher<View> parentMatcher, final int position) {
|
final Matcher<View> parentMatcher, final int position) {
|
||||||
|
|
||||||
return new TypeSafeMatcher<View>() {
|
return new TypeSafeMatcher<>() {
|
||||||
@Override
|
@Override
|
||||||
public void describeTo(Description description) {
|
public void describeTo(Description description) {
|
||||||
description.appendText("Child at position " + position + " in parent ");
|
description.appendText("Child at position " + position + " in parent ");
|
||||||
@@ -160,12 +160,14 @@ public class EspressoDeleteTrackTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Matcher<View> withListSize (final int size) {
|
public static Matcher<View> withListSize (final int size) {
|
||||||
return new TypeSafeMatcher<View> () {
|
return new TypeSafeMatcher<>() {
|
||||||
@Override public boolean matchesSafely (final View view) {
|
@Override
|
||||||
|
public boolean matchesSafely(final View view) {
|
||||||
return ((ListView) view).getCount() == size;
|
return ((ListView) view).getCount() == size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void describeTo (final Description description) {
|
@Override
|
||||||
|
public void describeTo(final Description description) {
|
||||||
description.appendText("ListView should have " + size + " items");
|
description.appendText("ListView should have " + size + " items");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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.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<>() {
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ import org.junit.rules.TestRule;
|
|||||||
import org.junit.runner.Description;
|
import org.junit.runner.Description;
|
||||||
import org.junit.runners.model.Statement;
|
import org.junit.runners.model.Statement;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
public class PreferenceMetricUnitRule implements TestRule {
|
public class PreferenceMetricUnitRule implements TestRule {
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@@ -25,14 +24,13 @@ public class PreferenceMetricUnitRule implements TestRule {
|
|||||||
return new Statement() {
|
return new Statement() {
|
||||||
@Override
|
@Override
|
||||||
public void evaluate() throws Throwable {
|
public void evaluate() throws Throwable {
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
final boolean previousMetricUnits = PreferencesUtils.isMetricUnits();
|
||||||
final boolean previousMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PreferencesUtils.setMetricUnits(sharedPreferences, context, metricUnits);
|
PreferencesUtils.setMetricUnits(metricUnits);
|
||||||
base.evaluate();
|
base.evaluate();
|
||||||
} finally {
|
} finally {
|
||||||
PreferencesUtils.setMetricUnits(sharedPreferences, context, previousMetricUnits);
|
PreferencesUtils.setMetricUnits(previousMetricUnits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
public class TestApplication extends Startup {
|
||||||
|
@Override
|
||||||
|
public String getDatabaseName() {
|
||||||
|
// null will make SQLiteOpenHelper create an in-memory database
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.runner.AndroidJUnitRunner;
|
||||||
|
|
||||||
|
public class TestRunner extends AndroidJUnitRunner {
|
||||||
|
@Override
|
||||||
|
public Application newApplication(ClassLoader cl, String className, Context context) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||||
|
return super.newApplication(cl, TestApplication.class.getName(), context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ public class ChartPointTest {
|
|||||||
// given
|
// given
|
||||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||||
trackPoint.setHeartRate_bpm(100f);
|
trackPoint.setHeartRate_bpm(100f);
|
||||||
trackPoint.setCyclingCadence_rpm(101f);
|
trackPoint.setCadence_rpm(101f);
|
||||||
trackPoint.setPower(102f);
|
trackPoint.setPower(102f);
|
||||||
|
|
||||||
TrackStatistics statistics = new TrackStatistics();
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ public class TestDataUtil {
|
|||||||
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
|
trackPoint.setSpeed(Speed.of(5f + (i / 10f)));
|
||||||
|
|
||||||
trackPoint.setHeartRate_bpm(100f + i);
|
trackPoint.setHeartRate_bpm(100f + i);
|
||||||
trackPoint.setCyclingCadence_rpm(300f + i);
|
trackPoint.setCadence_rpm(300f + i);
|
||||||
trackPoint.setPower(400f + i);
|
trackPoint.setPower(400f + i);
|
||||||
trackPoint.setAltitudeGain(ALTITUDE_GAIN);
|
trackPoint.setAltitudeGain(ALTITUDE_GAIN);
|
||||||
trackPoint.setAltitudeLoss(ALTITUDE_LOSS);
|
trackPoint.setAltitudeLoss(ALTITUDE_LOSS);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class TestSensorDataUtil {
|
|||||||
tp.setLatitude(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0);
|
tp.setLatitude(TestDataUtil.INITIAL_LATITUDE + (double) i / 10000.0);
|
||||||
tp.setLongitude(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0);
|
tp.setLongitude(TestDataUtil.INITIAL_LONGITUDE - (double) i / 10000.0);
|
||||||
tp.setHeartRate_bpm(hr);
|
tp.setHeartRate_bpm(hr);
|
||||||
tp.setCyclingCadence_rpm(cadence);
|
tp.setCadence_rpm(cadence);
|
||||||
tp.setPower(power);
|
tp.setPower(power);
|
||||||
tp.setHorizontalAccuracy(Distance.of(1f));
|
tp.setHorizontalAccuracy(Distance.of(1f));
|
||||||
tp.setAltitude(1f);
|
tp.setAltitude(1f);
|
||||||
|
|||||||
@@ -835,7 +835,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
|
|
||||||
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
|
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
|
||||||
trackPoint.setHeartRate_bpm(1F);
|
trackPoint.setHeartRate_bpm(1F);
|
||||||
trackPoint.setCyclingCadence_rpm(2F);
|
trackPoint.setCadence_rpm(2F);
|
||||||
trackPoint.setPower(3F);
|
trackPoint.setPower(3F);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -845,7 +845,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||||
assertTrue(trackPoints.get(10).hasHeartRate());
|
assertTrue(trackPoints.get(10).hasHeartRate());
|
||||||
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
|
assertEquals(trackPoint.getHeartRate_bpm(), trackPoints.get(10).getHeartRate_bpm(), 0.01);
|
||||||
assertEquals(trackPoint.getCyclingCadence_rpm(), trackPoints.get(10).getCyclingCadence_rpm(), 0.01);
|
assertEquals(trackPoint.getCadence_rpm(), trackPoints.get(10).getCadence_rpm(), 0.01);
|
||||||
assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower(), 0.01);
|
assertEquals(trackPoint.getPower(), trackPoints.get(10).getPower(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,7 +934,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
TrackPoint trackPoint = TestDataUtil.createTrackPoint(1);
|
TrackPoint trackPoint = TestDataUtil.createTrackPoint(1);
|
||||||
trackPoint.setType(TrackPoint.Type.TRACKPOINT);
|
trackPoint.setType(TrackPoint.Type.TRACKPOINT);
|
||||||
trackPoint.setPower(null);
|
trackPoint.setPower(null);
|
||||||
trackPoint.setCyclingCadence_rpm(null);
|
trackPoint.setCadence_rpm(null);
|
||||||
trackPoint.setHeartRate_bpm(null);
|
trackPoint.setHeartRate_bpm(null);
|
||||||
trackPointList.add(trackPoint);
|
trackPointList.add(trackPoint);
|
||||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.content.sensor;
|
|||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ import de.dennisguse.opentracks.util.UintUtils;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class SensorDataCyclingTest {
|
public class SensorDataCyclingTest {
|
||||||
@@ -81,7 +83,9 @@ public class SensorDataCyclingTest {
|
|||||||
assertEquals(60, current.getValue(), 0.01);
|
assertEquals(60, current.getValue(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore("Disabled from #953")
|
||||||
@Test
|
@Test
|
||||||
|
@Deprecated
|
||||||
public void compute_cadence_rollOverCount() {
|
public void compute_cadence_rollOverCount() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||||
@@ -94,6 +98,19 @@ public class SensorDataCyclingTest {
|
|||||||
assertEquals(60, current.getValue(), 0.01);
|
assertEquals(60, current.getValue(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compute_cadence_overflow() {
|
||||||
|
// given
|
||||||
|
SensorDataCycling.Cadence previous = new SensorDataCycling.Cadence("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||||
|
SensorDataCycling.Cadence current = new SensorDataCycling.Cadence("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertNull(current.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compute_speed() {
|
public void compute_speed() {
|
||||||
// given
|
// given
|
||||||
@@ -108,7 +125,9 @@ public class SensorDataCyclingTest {
|
|||||||
assertEquals(1.20, current.getValue().getSpeed().toMPS(), 0.01);
|
assertEquals(1.20, current.getValue().getSpeed().toMPS(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Ignore("Disabled from #953")
|
||||||
@Test
|
@Test
|
||||||
|
@Deprecated
|
||||||
public void compute_speed_rollOverCount() {
|
public void compute_speed_rollOverCount() {
|
||||||
// given
|
// given
|
||||||
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||||
@@ -122,6 +141,19 @@ public class SensorDataCyclingTest {
|
|||||||
assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
|
assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void compute_speed_overflow() {
|
||||||
|
// given
|
||||||
|
SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT32_MAX - 1, 1024);
|
||||||
|
SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048);
|
||||||
|
|
||||||
|
// when
|
||||||
|
current.compute(previous, Distance.ofMM(2000));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertNull(current.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void equals_speed_with_no_data() {
|
public void equals_speed_with_no_data() {
|
||||||
// given
|
// given
|
||||||
|
|||||||
@@ -112,41 +112,41 @@ public class ExportImportTest {
|
|||||||
|
|
||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||||
.getService();
|
.getService();
|
||||||
service.getHandlerServer().setClock(Clock.systemUTC());
|
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUp(boolean hasSensorDistance) throws TimeoutException {
|
public void setUp(boolean hasSensorDistance) throws TimeoutException {
|
||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||||
.getService();
|
.getService();
|
||||||
|
|
||||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||||
trackId = service.startNewTrack();
|
trackId = service.startNewTrack();
|
||||||
|
|
||||||
Distance sensorDistance = hasSensorDistance ? Distance.of(5) : null;
|
Distance sensorDistance = hasSensorDistance ? Distance.of(5) : null;
|
||||||
|
|
||||||
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance);
|
sendLocation(trackPointCreator, 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.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
|
||||||
sendLocation(handlerServer, Instant.parse("2020-02-02T02:02:04Z"), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, sensorDistance);
|
sendLocation(trackPointCreator, 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);
|
sendLocation(trackPointCreator, 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);
|
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")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:06Z"), ZoneId.of("CET")));
|
||||||
handlerServer.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
|
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
|
||||||
service.pauseCurrentTrack();
|
service.pauseCurrentTrack();
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:20Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:20Z"), ZoneId.of("CET")));
|
||||||
service.resumeCurrentTrack();
|
service.resumeCurrentTrack();
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:21Z"), ZoneId.of("CET")));
|
trackPointCreator.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(trackPointCreator, 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(trackPointCreator, 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);
|
sendLocation(trackPointCreator, 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")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:24Z"), ZoneId.of("CET")));
|
||||||
handlerServer.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
|
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context));
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
@@ -380,7 +380,7 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
private void sendLocation(TrackPointCreator trackPointCreator, 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 location = new Location("mock");
|
||||||
location.setLatitude(latitude);
|
location.setLatitude(latitude);
|
||||||
location.setLongitude(longitude);
|
location.setLongitude(longitude);
|
||||||
@@ -388,7 +388,7 @@ public class ExportImportTest {
|
|||||||
location.setSpeed(speed);
|
location.setSpeed(speed);
|
||||||
location.setAltitude(altitude);
|
location.setAltitude(altitude);
|
||||||
|
|
||||||
handlerServer.setAltitudeSumManager(new AltitudeSumManager() {
|
trackPointCreator.setAltitudeSumManager(new AltitudeSumManager() {
|
||||||
@Override
|
@Override
|
||||||
public void fill(@NonNull TrackPoint trackPoint) {
|
public void fill(@NonNull TrackPoint trackPoint) {
|
||||||
trackPoint.setAltitudeGain(altitudeGain);
|
trackPoint.setAltitudeGain(altitudeGain);
|
||||||
@@ -396,7 +396,7 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
handlerServer.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
|
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
|
||||||
@Override
|
@Override
|
||||||
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
||||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||||
@@ -422,7 +422,7 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(time, ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET")));
|
||||||
handlerServer.getLocationHandler().onLocationChanged(location);
|
trackPointCreator.getLocationHandler().onLocationChanged(location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ import org.junit.runners.JUnit4;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ import de.dennisguse.opentracks.content.data.TestDataUtil;
|
|||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
@@ -67,7 +69,7 @@ public class GPXImportTest {
|
|||||||
importTrackId = importer.importFile(inputStream).get(0);
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
// 1. track
|
// 2. track
|
||||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
assertNotNull(importedTrack);
|
assertNotNull(importedTrack);
|
||||||
assertEquals("the category", importedTrack.getCategory());
|
assertEquals("the category", importedTrack.getCategory());
|
||||||
@@ -75,6 +77,8 @@ public class GPXImportTest {
|
|||||||
assertEquals("2021-01-07 22:51", importedTrack.getName());
|
assertEquals("2021-01-07 22:51", importedTrack.getName());
|
||||||
assertEquals("UNKNOWN", importedTrack.getIcon());
|
assertEquals("UNKNOWN", importedTrack.getIcon());
|
||||||
|
|
||||||
|
//TODO Check trackstatistics
|
||||||
|
|
||||||
// 3. trackpoints
|
// 3. trackpoints
|
||||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||||
assertEquals(6, importedTrackPoints.size());
|
assertEquals(6, importedTrackPoints.size());
|
||||||
@@ -106,4 +110,102 @@ public class GPXImportTest {
|
|||||||
.setAltitude(10)
|
.setAltitude(10)
|
||||||
), importedTrackPoints);
|
), importedTrackPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void gpx_without_speed() throws IOException {
|
||||||
|
// given
|
||||||
|
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
|
||||||
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_without_speed);
|
||||||
|
|
||||||
|
// when
|
||||||
|
// 1. import
|
||||||
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
|
// then
|
||||||
|
// 2. track
|
||||||
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
|
assertNotNull(importedTrack);
|
||||||
|
assertEquals("", importedTrack.getCategory());
|
||||||
|
assertEquals("", importedTrack.getDescription());
|
||||||
|
assertEquals("20210907_213924.gpx", importedTrack.getName());
|
||||||
|
assertEquals("", importedTrack.getIcon());
|
||||||
|
|
||||||
|
// 3. trackstatistics
|
||||||
|
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
|
||||||
|
assertEquals(1.44, trackStatistics.getMaxSpeed().toMPS(), 0.01);
|
||||||
|
assertEquals(Duration.ofSeconds(53), trackStatistics.getMovingTime());
|
||||||
|
|
||||||
|
// 4. trackpoints
|
||||||
|
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||||
|
assertEquals(3, importedTrackPoints.size());
|
||||||
|
|
||||||
|
TrackPointAssert a = new TrackPointAssert();
|
||||||
|
a.assertEquals(List.of(
|
||||||
|
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2021-09-07T22:10:19Z"))
|
||||||
|
.setLatitude(30.14185982)
|
||||||
|
.setLongitude(-40.3863038)
|
||||||
|
.setAltitude(-5),
|
||||||
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:11:07Z"))
|
||||||
|
.setLatitude(30.14184657)
|
||||||
|
.setLongitude(-40.38670089)
|
||||||
|
.setAltitude(-5)
|
||||||
|
.setSpeed(Speed.of(0.7976524233818054)),
|
||||||
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:12:00Z"))
|
||||||
|
.setLatitude(30.14185982)
|
||||||
|
.setLongitude(-40.3863038)
|
||||||
|
.setAltitude(-5)
|
||||||
|
.setSpeed(Speed.of(0.7224021553993225))
|
||||||
|
), importedTrackPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@Test
|
||||||
|
public void gpx_speed_no_namespace() throws IOException {
|
||||||
|
// given
|
||||||
|
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
|
||||||
|
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_with_speed_no_namespace);
|
||||||
|
|
||||||
|
// when
|
||||||
|
// 1. import
|
||||||
|
importTrackId = importer.importFile(inputStream).get(0);
|
||||||
|
|
||||||
|
// then
|
||||||
|
// 2. track
|
||||||
|
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||||
|
assertNotNull(importedTrack);
|
||||||
|
assertEquals("", importedTrack.getCategory());
|
||||||
|
assertEquals("", importedTrack.getDescription());
|
||||||
|
assertEquals("20210907_213924.gpx", importedTrack.getName());
|
||||||
|
assertEquals("", importedTrack.getIcon());
|
||||||
|
|
||||||
|
// 3. trackstatistics
|
||||||
|
TrackStatistics trackStatistics = importedTrack.getTrackStatistics();
|
||||||
|
assertEquals(4, trackStatistics.getMaxSpeed().toMPS(), 0.01);
|
||||||
|
assertEquals(Duration.ofSeconds(101), trackStatistics.getMovingTime());
|
||||||
|
|
||||||
|
// 4. trackpoints
|
||||||
|
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||||
|
assertEquals(3, importedTrackPoints.size());
|
||||||
|
|
||||||
|
TrackPointAssert a = new TrackPointAssert();
|
||||||
|
a.assertEquals(List.of(
|
||||||
|
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, Instant.parse("2021-09-07T22:10:19Z"))
|
||||||
|
.setLatitude(30.14185982)
|
||||||
|
.setLongitude(-40.3863038)
|
||||||
|
.setAltitude(-5)
|
||||||
|
.setSpeed(Speed.of(5)),
|
||||||
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:11:07Z"))
|
||||||
|
.setLatitude(30.14184657)
|
||||||
|
.setLongitude(-40.38670089)
|
||||||
|
.setAltitude(-5)
|
||||||
|
.setSpeed(Speed.of(0.7976524233818054))
|
||||||
|
.setSpeed(Speed.of(4)),
|
||||||
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2021-09-07T22:12:00Z"))
|
||||||
|
.setLatitude(30.14185982)
|
||||||
|
.setLongitude(-40.3863038)
|
||||||
|
.setAltitude(-5)
|
||||||
|
.setSpeed(Speed.of(3))
|
||||||
|
), importedTrackPoints);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -73,9 +73,9 @@ public class TrackPointAssert {
|
|||||||
Assert.assertEquals(expected.getPower(), actual.getPower(), delta);
|
Assert.assertEquals(expected.getPower(), actual.getPower(), delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertEquals(expected.hasCyclingCadence(), actual.hasCyclingCadence());
|
Assert.assertEquals(expected.hasCadence(), actual.hasCadence());
|
||||||
if (expected.hasCyclingCadence()) {
|
if (expected.hasCadence()) {
|
||||||
Assert.assertEquals(expected.getCyclingCadence_rpm(), actual.getCyclingCadence_rpm(), delta);
|
Assert.assertEquals(expected.getCadence_rpm(), actual.getCadence_rpm(), delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,14 +83,14 @@ public class TrackPointAssert {
|
|||||||
try {
|
try {
|
||||||
Assert.assertEquals(expected.size(), actual.size());
|
Assert.assertEquals(expected.size(), actual.size());
|
||||||
} catch (AssertionError e) {
|
} catch (AssertionError e) {
|
||||||
throw new AssertionError("Expected: " + expected + " actual: " + actual);
|
throw new AssertionError("Expected: " + expected + "\n actual: " + actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < expected.size(); i++) {
|
for (int i = 0; i < expected.size(); i++) {
|
||||||
try {
|
try {
|
||||||
assertEquals(expected.get(i), actual.get(i));
|
assertEquals(expected.get(i), actual.get(i));
|
||||||
} catch (AssertionError e) {
|
} catch (AssertionError e) {
|
||||||
throw new AssertionError("Expected: " + expected.get(i) + " actual: " + actual.get(i), e);
|
throw new AssertionError("Expected: " + expected.get(i) + "\n actual: " + actual.get(i), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Assert.assertEquals(expected.size(), actual.size());
|
Assert.assertEquals(expected.size(), actual.size());
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package de.dennisguse.opentracks.services;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.services.handlers.AdaptiveLocationListenerPolicy;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the {@link AdaptiveLocationListenerPolicy}.
|
|
||||||
*
|
|
||||||
* @author youtaol
|
|
||||||
*/
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class AdaptiveLocationListenerPolicyTest {
|
|
||||||
|
|
||||||
private static final Duration MIN = Duration.ofMillis(1000);
|
|
||||||
private static final Duration MAX = Duration.ofMillis(3000);
|
|
||||||
private static final int MIN_DISTANCE = 10;
|
|
||||||
private static final Duration NEW_IDLE_TIME_BIG = Duration.ofMillis(10000);
|
|
||||||
private static final Duration NEW_IDLE_TIME_NORMAL = Duration.ofMillis(5000);
|
|
||||||
private static final Duration NEW_IDLE_TIME_SMALL = Duration.ofMillis(2000);
|
|
||||||
private static final Duration NEW_IDLE_TIME_LESS_THAN_MIN = Duration.ofMillis(500);
|
|
||||||
private AdaptiveLocationListenerPolicy adaptiveLocationListenerPolicy;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
adaptiveLocationListenerPolicy = new AdaptiveLocationListenerPolicy(MIN, MAX, MIN_DISTANCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the {@link AdaptiveLocationListenerPolicy#getDesiredPollingInterval()} in four situations.
|
|
||||||
* <ul>
|
|
||||||
* <li>The newIdleTime is bigger than max interval.</li>
|
|
||||||
* <li>The newIdleTime is between min and max interval.</li>
|
|
||||||
* <li>The newIdleTime is smaller than max interval.</li>
|
|
||||||
* <li>The newIdleTime is smaller than the smallest interval unit.</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetDesiredPollingInterval() {
|
|
||||||
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_BIG);
|
|
||||||
assertEquals(MAX, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
|
|
||||||
|
|
||||||
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_NORMAL);
|
|
||||||
// First get the half of NEW_IDLE_TIME_NORMAL, and then round it to the nearest second.
|
|
||||||
assertEquals(NEW_IDLE_TIME_NORMAL.dividedBy(2).getSeconds(), adaptiveLocationListenerPolicy.getDesiredPollingInterval().getSeconds());
|
|
||||||
|
|
||||||
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_SMALL);
|
|
||||||
assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
|
|
||||||
|
|
||||||
adaptiveLocationListenerPolicy.updateIdleTime(NEW_IDLE_TIME_LESS_THAN_MIN);
|
|
||||||
assertEquals(MIN, adaptiveLocationListenerPolicy.getDesiredPollingInterval());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the method {@link AdaptiveLocationListenerPolicy#getMinDistance_m()}.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testGetMinDistance() {
|
|
||||||
assertEquals(MIN_DISTANCE, adaptiveLocationListenerPolicy.getMinDistance_m());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,6 @@ import static org.junit.Assert.assertTrue;
|
|||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|
||||||
@@ -66,15 +65,12 @@ import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
|||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the track recording service.
|
* Tests for the track recording service.
|
||||||
*
|
*
|
||||||
* @author Bartlomiej Niechwiej
|
* @author Bartlomiej Niechwiej
|
||||||
* <p>
|
|
||||||
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
|
|
||||||
* So, if it is executed on a real device, data might be lost.
|
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class TrackRecordingServiceTest {
|
public class TrackRecordingServiceTest {
|
||||||
@@ -97,7 +93,6 @@ public class TrackRecordingServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
|
private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
|
||||||
@@ -124,7 +119,7 @@ public class TrackRecordingServiceTest {
|
|||||||
contentProviderUtils = new ContentProviderUtils(context);
|
contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
sharedPreferences.edit().clear().commit();
|
PreferencesUtils.clear();
|
||||||
|
|
||||||
// Ensure that the database is empty before every test
|
// Ensure that the database is empty before every test
|
||||||
contentProviderUtils.deleteAllTracks(context);
|
contentProviderUtils.deleteAllTracks(context);
|
||||||
@@ -138,7 +133,7 @@ public class TrackRecordingServiceTest {
|
|||||||
if (service.isRecording() || service.isPaused()) {
|
if (service.isRecording() || service.isPaused()) {
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
}
|
}
|
||||||
service.getHandlerServer().setClock(Clock.systemUTC());
|
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
||||||
|
|
||||||
// Ensure that the database is empty after every test
|
// Ensure that the database is empty after every test
|
||||||
contentProviderUtils.deleteAllTracks(context);
|
contentProviderUtils.deleteAllTracks(context);
|
||||||
@@ -212,15 +207,15 @@ public class TrackRecordingServiceTest {
|
|||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||||
.getService();
|
.getService();
|
||||||
|
|
||||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
handlerServer.stopGPS();
|
trackPointCreator.stopGPS();
|
||||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -242,22 +237,22 @@ public class TrackRecordingServiceTest {
|
|||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||||
.getService();
|
.getService();
|
||||||
|
|
||||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
handlerServer.stopGPS();
|
trackPointCreator.stopGPS();
|
||||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
||||||
service.pauseCurrentTrack();
|
service.pauseCurrentTrack();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
assertEquals(2, contentProviderUtils.getTrackPointCursor(trackId, null).getCount());
|
||||||
|
|
||||||
//when
|
//when
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
|
||||||
service.resumeTrack(trackId);
|
service.resumeTrack(trackId);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -281,23 +276,23 @@ public class TrackRecordingServiceTest {
|
|||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||||
.getService();
|
.getService();
|
||||||
|
|
||||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
handlerServer.stopGPS();
|
trackPointCreator.stopGPS();
|
||||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
|
||||||
service.resumeTrack(trackId);
|
service.resumeTrack(trackId);
|
||||||
handlerServer.stopGPS();
|
trackPointCreator.stopGPS();
|
||||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
handlerServer.onNewTrackPoint(new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z")), Distance.of(50));
|
trackPointCreator.onNewTrackPoint(new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:05Z")), Distance.of(50));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
@@ -323,18 +318,18 @@ public class TrackRecordingServiceTest {
|
|||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||||
.getService();
|
.getService();
|
||||||
|
|
||||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
handlerServer.stopGPS();
|
trackPointCreator.stopGPS();
|
||||||
handlerServer.setAltitudeSumManager(altitudeSumManager);
|
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:03Z"), ZoneId.of("CET")));
|
||||||
service.pauseCurrentTrack();
|
service.pauseCurrentTrack();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:04Z"), ZoneId.of("CET")));
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@@ -405,14 +400,14 @@ public class TrackRecordingServiceTest {
|
|||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(createStartIntent(context)))
|
||||||
.getService();
|
.getService();
|
||||||
|
|
||||||
TrackPointCreator handlerServer = service.getHandlerServer();
|
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||||
handlerServer.stopGPS();
|
trackPointCreator.stopGPS();
|
||||||
|
|
||||||
handlerServer.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
|
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
handlerServer.onNewTrackPoint(
|
trackPointCreator.onNewTrackPoint(
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z"))
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z"))
|
||||||
.setLatitude(10)
|
.setLatitude(10)
|
||||||
.setLongitude(10)
|
.setLongitude(10)
|
||||||
@@ -467,6 +462,6 @@ public class TrackRecordingServiceTest {
|
|||||||
.setSpeed(Speed.of(speed))
|
.setSpeed(Speed.of(speed))
|
||||||
.setBearing(3.0f);
|
.setBearing(3.0f);
|
||||||
|
|
||||||
trackRecordingService.getHandlerServer().onNewTrackPoint(trackPoint, Distance.of(50));
|
trackRecordingService.getTrackPointCreator().onNewTrackPoint(trackPoint, Distance.of(50));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package de.dennisguse.opentracks.services;
|
package de.dennisguse.opentracks.services;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -19,11 +20,14 @@ import org.junit.BeforeClass;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.internal.debugging.LocationImpl;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||||
@@ -32,19 +36,15 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
|
|||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
|
||||||
|
import de.dennisguse.opentracks.content.sensor.SensorDataRunning;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
||||||
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
||||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests insert location.
|
* Tests insert location.
|
||||||
* <p>
|
|
||||||
* //TODO ATTENTION: This tests deletes all stored tracks in the database.
|
|
||||||
* So, if it is executed on a real device, data might be lost.
|
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
//TODO Implement as mock test; no need to store data in database
|
//TODO Implement as mock test; no need to store data in database
|
||||||
@@ -91,12 +91,11 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
tearDown();
|
tearDown();
|
||||||
|
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
PreferencesUtils.clear();
|
||||||
sharedPreferences.edit().clear().commit();
|
|
||||||
|
|
||||||
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
|
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
|
||||||
.getService();
|
.getService();
|
||||||
service.getHandlerServer().stopGPS();
|
service.getTrackPointCreator().stopGPS();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -107,10 +106,10 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testOnLocationChangedAsync_movingAccurate() throws Exception {
|
public void testOnLocationChangedAsync_movingAccurate() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||||
@@ -180,10 +179,10 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
|
public void testOnLocationChangedAsync_slowMovingAccurate() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||||
@@ -191,7 +190,7 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 3, 15);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 3, 15);
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 4, 15);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 4, 15);
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000004, 35.0, 5, 15);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.000004, 35.0, 5, 15);
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000005, 35.0, 6, 15);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 6, 15);
|
||||||
|
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
@@ -211,7 +210,7 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f),
|
.setAltitudeLoss(0f),
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||||
.setLatitude(45.0005)
|
.setLatitude(45.0002)
|
||||||
.setLongitude(35)
|
.setLongitude(35)
|
||||||
.setHorizontalAccuracy(Distance.of(6))
|
.setHorizontalAccuracy(Distance.of(6))
|
||||||
.setSpeed(Speed.of(15))
|
.setSpeed(Speed.of(15))
|
||||||
@@ -221,14 +220,17 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f)
|
.setAltitudeLoss(0f)
|
||||||
), trackPoints);
|
), trackPoints);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testOnLocationChangedAsync_idle() throws Exception {
|
public void testOnLocationChangedAsync_idle() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||||
@@ -255,20 +257,6 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setSpeed(Speed.of(0))
|
.setSpeed(Speed.of(0))
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f),
|
.setAltitudeLoss(0f),
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Why is this added? Systems is idle and not moving at all.
|
|
||||||
.setLatitude(45)
|
|
||||||
.setLongitude(35)
|
|
||||||
.setHorizontalAccuracy(Distance.of(2))
|
|
||||||
.setSpeed(Speed.of(0))
|
|
||||||
.setAltitudeGain(0f)
|
|
||||||
.setAltitudeLoss(0f),
|
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
|
||||||
.setLatitude(45)
|
|
||||||
.setLongitude(35)
|
|
||||||
.setHorizontalAccuracy(Distance.of(6))
|
|
||||||
.setSpeed(Speed.of(0))
|
|
||||||
.setAltitudeGain(0f)
|
|
||||||
.setAltitudeLoss(0f),
|
|
||||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f)
|
.setAltitudeLoss(0f)
|
||||||
@@ -277,11 +265,11 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testOnLocationChangedAsync_idle_withMovement() throws Exception {
|
public void testOnLocationChangedAsync_idle_withMovement() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
|
||||||
service.getHandlerServer().stopGPS();
|
service.getTrackPointCreator().stopGPS();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||||
@@ -308,27 +296,6 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setSpeed(Speed.of(15))
|
.setSpeed(Speed.of(15))
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f),
|
.setAltitudeLoss(0f),
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
|
|
||||||
.setLatitude(45)
|
|
||||||
.setLongitude(35)
|
|
||||||
.setHorizontalAccuracy(Distance.of(2))
|
|
||||||
.setSpeed(Speed.of(0))
|
|
||||||
.setAltitudeGain(0f)
|
|
||||||
.setAltitudeLoss(0f),
|
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null) //TODO Check why this trackPoint is inserted.
|
|
||||||
.setLatitude(45)
|
|
||||||
.setLongitude(35)
|
|
||||||
.setHorizontalAccuracy(Distance.of(5))
|
|
||||||
.setSpeed(Speed.of(0))
|
|
||||||
.setAltitudeGain(0f)
|
|
||||||
.setAltitudeLoss(0f),
|
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
|
||||||
.setLatitude(45)
|
|
||||||
.setLongitude(35)
|
|
||||||
.setHorizontalAccuracy(Distance.of(6))
|
|
||||||
.setSpeed(Speed.of(15))
|
|
||||||
.setAltitudeGain(0f)
|
|
||||||
.setAltitudeLoss(0f),
|
|
||||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f)
|
.setAltitudeLoss(0f)
|
||||||
@@ -337,11 +304,11 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
|
public void testOnLocationChangedAsync_idle_withSensorData() {
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
|
||||||
service.getHandlerServer().setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
|
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
@@ -430,12 +397,86 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
), trackPoints);
|
), trackPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated // Will be superseded when fixing #500
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testOnLocationChangedAsync_segment() throws Exception {
|
public void testOnLocationChangedAsync_idle_withSensorDistance() {
|
||||||
|
BluetoothRemoteSensorManager remoteSensorManager = new BluetoothRemoteSensorManager(context) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AltitudeSumManager altitudeSumManager = new AltitudeSumManager();
|
||||||
|
|
||||||
// given
|
// given
|
||||||
Track.Id trackId = service.startNewTrack();
|
Track.Id trackId = service.startNewTrack();
|
||||||
service.getHandlerServer().setAltitudeSumManager(altitudeSumManager);
|
service.getTrackPointCreator().setRemoteSensorManager(remoteSensorManager);
|
||||||
|
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
altitudeSumManager.stop(service);
|
||||||
|
altitudeSumManager.setConnected(true);
|
||||||
|
|
||||||
|
// when
|
||||||
|
altitudeSumManager.addAltitudeGain_m(6f);
|
||||||
|
altitudeSumManager.addAltitudeLoss_m(6f);
|
||||||
|
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(0)));
|
||||||
|
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(2)));
|
||||||
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||||
|
|
||||||
|
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(12)));
|
||||||
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 15);
|
||||||
|
|
||||||
|
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(13)));
|
||||||
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 15);
|
||||||
|
altitudeSumManager.addAltitudeGain_m(6f);
|
||||||
|
altitudeSumManager.addAltitudeLoss_m(6f);
|
||||||
|
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(14)));
|
||||||
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 15);
|
||||||
|
|
||||||
|
altitudeSumManager.addAltitudeGain_m(6f);
|
||||||
|
altitudeSumManager.addAltitudeLoss_m(6f);
|
||||||
|
remoteSensorManager.onChanged(new SensorDataRunning("", "", Speed.of(5), null, Distance.of(16)));
|
||||||
|
service.endCurrentTrack();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(service.isRecording());
|
||||||
|
|
||||||
|
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||||
|
TrackPointAssert a = new TrackPointAssert()
|
||||||
|
.ignoreTime();
|
||||||
|
a.assertEquals(List.of(
|
||||||
|
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, null),
|
||||||
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||||
|
.setLatitude(45)
|
||||||
|
.setLongitude(35)
|
||||||
|
.setHorizontalAccuracy(Distance.of(1))
|
||||||
|
.setSpeed(Speed.of(5))
|
||||||
|
.setAltitudeGain(6f)
|
||||||
|
.setAltitudeLoss(6f)
|
||||||
|
.setSensorDistance(Distance.of(2)),
|
||||||
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||||
|
.setLatitude(45)
|
||||||
|
.setLongitude(35)
|
||||||
|
.setHorizontalAccuracy(Distance.of(2))
|
||||||
|
.setSpeed(Speed.of(5))
|
||||||
|
.setSensorDistance(Distance.of(10)),
|
||||||
|
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||||
|
.setSensorDistance(Distance.of(11))
|
||||||
|
.setSpeed(Speed.of(5))
|
||||||
|
.setAltitudeGain(12f)
|
||||||
|
.setAltitudeLoss(12f)
|
||||||
|
.setSensorDistance(Distance.of(4))
|
||||||
|
), trackPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
@MediumTest
|
||||||
|
@Test
|
||||||
|
public void testOnLocationChangedAsync_segment() {
|
||||||
|
// given
|
||||||
|
Track.Id trackId = service.startNewTrack();
|
||||||
|
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
|
||||||
@@ -470,13 +511,6 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setSpeed(Speed.of(0))
|
.setSpeed(Speed.of(0))
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f),
|
.setAltitudeLoss(0f),
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
|
||||||
.setLatitude(45.1)
|
|
||||||
.setLongitude(35)
|
|
||||||
.setHorizontalAccuracy(Distance.of(3))
|
|
||||||
.setSpeed(Speed.of(0))
|
|
||||||
.setAltitudeGain(0f)
|
|
||||||
.setAltitudeLoss(0f),
|
|
||||||
|
|
||||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null)
|
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null)
|
||||||
.setLatitude(45.2)
|
.setLatitude(45.2)
|
||||||
@@ -485,13 +519,6 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setSpeed(Speed.of(0))
|
.setSpeed(Speed.of(0))
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f),
|
.setAltitudeLoss(0f),
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
|
||||||
.setLatitude(45.2)
|
|
||||||
.setLongitude(35)
|
|
||||||
.setHorizontalAccuracy(Distance.of(5))
|
|
||||||
.setSpeed(Speed.of(0))
|
|
||||||
.setAltitudeGain(0f)
|
|
||||||
.setAltitudeLoss(0f),
|
|
||||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f)
|
.setAltitudeLoss(0f)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
|
|||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -43,9 +43,6 @@ import static org.junit.Assert.assertTrue;
|
|||||||
* Tests for the track recording service, which require a {@link Looper}.
|
* Tests for the track recording service, which require a {@link Looper}.
|
||||||
*
|
*
|
||||||
* @author Bartlomiej Niechwiej
|
* @author Bartlomiej Niechwiej
|
||||||
* <p>
|
|
||||||
* ATTENTION: This tests deletes all stored tracks in the database.
|
|
||||||
* So, if it is executed on a real device, data might be lost.
|
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class TrackRecordingServiceTestLooper {
|
public class TrackRecordingServiceTestLooper {
|
||||||
@@ -54,7 +51,6 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
|
||||||
|
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
|
|
||||||
@@ -79,7 +75,7 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
contentProviderUtils = new ContentProviderUtils(context);
|
contentProviderUtils = new ContentProviderUtils(context);
|
||||||
|
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
sharedPreferences.edit().clear().apply();
|
PreferencesUtils.clear();
|
||||||
|
|
||||||
// Ensure that the database is empty before every test
|
// Ensure that the database is empty before every test
|
||||||
contentProviderUtils.deleteAllTracks(context);
|
contentProviderUtils.deleteAllTracks(context);
|
||||||
@@ -107,34 +103,6 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_voiceFrequencyDefault() throws TimeoutException {
|
|
||||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
|
||||||
editor.putInt(context.getString(R.string.voice_frequency_key), Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default)));
|
|
||||||
editor.commit();
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_voiceFrequencyByDistance() throws TimeoutException {
|
|
||||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
|
||||||
editor.putInt(context.getString(R.string.voice_frequency_key), -1);
|
|
||||||
editor.commit();
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
@Test
|
|
||||||
public void testWithProperties_voiceFrequencyByTime() throws TimeoutException {
|
|
||||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
|
||||||
editor.putInt(context.getString(R.string.voice_frequency_key), 1);
|
|
||||||
editor.commit();
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
public void testWithProperties_maxRecordingDistanceDefault() throws TimeoutException {
|
||||||
@@ -174,14 +142,14 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
public void testWithProperties_metricUnitsDefault() throws TimeoutException {
|
||||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
PreferencesUtils.setString(R.string.stats_units_key, context.getString(R.string.stats_units_default));
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@Test
|
@Test
|
||||||
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
|
public void testWithProperties_metricUnitsDisabled() throws TimeoutException {
|
||||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
PreferencesUtils.setString(R.string.stats_units_key, context.getString(R.string.stats_units_imperial));
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,16 +203,16 @@ public class TrackRecordingServiceTestLooper {
|
|||||||
|
|
||||||
// Insert a few points, markers and statistics.
|
// Insert a few points, markers and statistics.
|
||||||
for (int i = 0; i < 30; i++) {
|
for (int i = 0; i < 30; i++) {
|
||||||
//TODO Should send locations to LocationHandler instead of TrackPoints to HandlerServer?
|
//TODO Should send locations to LocationHandler instead of TrackPoints to TrackPointCreator?
|
||||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, service.getHandlerServer().createNow())
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, service.getTrackPointCreator().createNow())
|
||||||
.setLongitude(35.0f + i / 10.0f)
|
.setLongitude(35.0f + i / 10.0f)
|
||||||
.setLatitude(45.0f - i / 5.0f)
|
.setLatitude(45.0f - i / 5.0f)
|
||||||
.setHorizontalAccuracy(Distance.of(5))
|
.setHorizontalAccuracy(Distance.of(5))
|
||||||
.setSpeed(Speed.of(10))
|
.setSpeed(Speed.of(10))
|
||||||
.setBearing(3.0f);
|
.setBearing(3.0f);
|
||||||
|
|
||||||
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
|
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
|
||||||
service.getHandlerServer().onNewTrackPoint(trackPoint, prefAccuracy);
|
service.getTrackPointCreator().onNewTrackPoint(trackPoint, prefAccuracy);
|
||||||
|
|
||||||
if (i % 7 == 0) {
|
if (i % 7 == 0) {
|
||||||
service.insertMarker(null, null, null, null);
|
service.insertMarker(null, null, null, null);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import static org.mockito.Mockito.times;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|
||||||
@@ -24,16 +23,15 @@ import java.time.Instant;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class LocationHandlerTest {
|
public class LocationHandlerTest {
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private final SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private TrackPointCreator handlerServer;
|
private TrackPointCreator trackPointCreator;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private LocationHandler locationHandler;
|
private LocationHandler locationHandler;
|
||||||
@@ -47,15 +45,12 @@ public class LocationHandlerTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
// Let's use default values.
|
// Let's use default values.
|
||||||
sharedPreferences.edit().clear().commit();
|
PreferencesUtils.clear();
|
||||||
|
|
||||||
Mockito.when(handlerServer.createNow())
|
Mockito.when(trackPointCreator.createNow())
|
||||||
.thenReturn(Instant.now());
|
.thenReturn(Instant.now());
|
||||||
|
|
||||||
//TODO REMOVE
|
locationHandler.onStart(context);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,7 +62,7 @@ public class LocationHandlerTest {
|
|||||||
locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis()));
|
locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis()));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(handlerServer, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,7 +78,7 @@ public class LocationHandlerTest {
|
|||||||
locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis()));
|
locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis()));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(handlerServer, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,14 +87,14 @@ public class LocationHandlerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testOnLocationChanged_poorAccuracy() {
|
public void testOnLocationChanged_poorAccuracy() {
|
||||||
// given
|
// given
|
||||||
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy(sharedPreferences, context);
|
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
locationHandler.onLocationChanged(createLocation(45f, 35f, (float) (prefAccuracy.toM() + 1), 5, System.currentTimeMillis()));
|
locationHandler.onLocationChanged(createLocation(45f, 35f, (float) (prefAccuracy.toM() + 1), 5, System.currentTimeMillis()));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
// no newTrackPoint called
|
// no newTrackPoint called
|
||||||
verify(handlerServer, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
verify(trackPointCreator, times(0)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -112,7 +107,7 @@ public class LocationHandlerTest {
|
|||||||
locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
verify(handlerServer, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
verify(trackPointCreator, times(1)).onNewTrackPoint(any(TrackPoint.class), any(Distance.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.services.tasks;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@@ -6,6 +6,7 @@ import android.util.Pair;
|
|||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -18,7 +19,9 @@ import de.dennisguse.opentracks.content.data.TestDataUtil;
|
|||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||||
|
import de.dennisguse.opentracks.services.tasks.AnnouncementUtils;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -68,7 +71,7 @@ public class AnnouncementUtilsTest {
|
|||||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
|
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, true, lastInterval);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(
|
Assert.assertEquals(
|
||||||
"total distance " +
|
"total distance " +
|
||||||
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
|
StringUtils.getDistanceParts(context, stats.getTotalDistance(), true).first +
|
||||||
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
|
" kilometers in " + buildAndGetTimeText(stats.getTotalTime(), false) + " at " +
|
||||||
@@ -15,6 +15,7 @@ import org.junit.Rule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class PeriodicTaskExecutorTest {
|
public class VoiceAnnouncementManagerTest {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||||
@@ -53,17 +54,38 @@ public class PeriodicTaskExecutorTest {
|
|||||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||||
.getService();
|
.getService();
|
||||||
|
|
||||||
PeriodicTaskExecutor periodicTaskExecutor = new PeriodicTaskExecutor(service, new AnnouncementPeriodicTask.Factory());
|
VoiceAnnouncementManager voiceAnnouncementManager = new VoiceAnnouncementManager(service);
|
||||||
periodicTaskExecutor.setMetricUnits(true);
|
voiceAnnouncementManager.setFrequency(Distance.ofKilometer(5));
|
||||||
periodicTaskExecutor.setTaskFrequency(-5);
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackStatistics statistics = new TrackStatistics();
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
statistics.setTotalDistance(Distance.of(13000));
|
statistics.setTotalDistance(Distance.ofKilometer(13));
|
||||||
assertEquals(Distance.of(15000), periodicTaskExecutor.calculateNextTaskDistance(statistics));
|
voiceAnnouncementManager.restore(statistics);
|
||||||
|
assertEquals(Distance.of(15000), voiceAnnouncementManager.getNextTotalDistance());
|
||||||
|
|
||||||
statistics.setTotalDistance(Distance.of(15100));
|
statistics.setTotalDistance(Distance.of(15100));
|
||||||
assertEquals(Distance.of(20000), periodicTaskExecutor.calculateNextTaskDistance(statistics));
|
voiceAnnouncementManager.restore(statistics);
|
||||||
|
assertEquals(Distance.of(20000), voiceAnnouncementManager.getNextTotalDistance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void calculateNextTotalTime() throws TimeoutException {
|
||||||
|
// given
|
||||||
|
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||||
|
.getService();
|
||||||
|
|
||||||
|
VoiceAnnouncementManager voiceAnnouncementManager = new VoiceAnnouncementManager(service);
|
||||||
|
voiceAnnouncementManager.setFrequency(Duration.ofSeconds(5));
|
||||||
|
|
||||||
|
// when
|
||||||
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
statistics.setTotalTime(Duration.ofSeconds(91));
|
||||||
|
voiceAnnouncementManager.restore(statistics);
|
||||||
|
assertEquals(Duration.ofSeconds(95), voiceAnnouncementManager.getNextTotalTime());
|
||||||
|
|
||||||
|
statistics.setTotalTime(Duration.ofSeconds(95));
|
||||||
|
voiceAnnouncementManager.restore(statistics);
|
||||||
|
assertEquals(Duration.ofSeconds(100), voiceAnnouncementManager.getNextTotalTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -93,8 +93,8 @@ public class BluetoothUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseRunningSpeedAndCadence_with_distance() {
|
public void parseRunningSpeedAndCadence_with_distance() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, 0, 0);
|
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_CHAR_UUID, 0, 0);
|
||||||
characteristic.setValue(new byte[]{2, 0, 5, 80, 12, 1});
|
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
|
||||||
|
|
||||||
// when
|
// when
|
||||||
SensorDataRunning sensor = BluetoothUtils.parseRunningSpeedAndCadence("address", "sensorName", characteristic);
|
SensorDataRunning sensor = BluetoothUtils.parseRunningSpeedAndCadence("address", "sensorName", characteristic);
|
||||||
@@ -102,6 +102,6 @@ public class BluetoothUtilsTest {
|
|||||||
// then
|
// then
|
||||||
assertEquals(Speed.of(5), sensor.getSpeed());
|
assertEquals(Speed.of(5), sensor.getSpeed());
|
||||||
assertEquals(80, sensor.getCadence(), 0.01);
|
assertEquals(80, sensor.getCadence(), 0.01);
|
||||||
assertEquals(Distance.of(26.8), sensor.getTotalDistance());
|
assertEquals(Distance.of(6553.5 + 1677721.6), sensor.getTotalDistance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.util;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
@@ -10,9 +11,15 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
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 de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
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)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class PreferencesUtilsTest {
|
public class PreferencesUtilsTest {
|
||||||
@@ -22,14 +29,14 @@ public class PreferencesUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void ExportTrackFileFormat_ok() {
|
public void ExportTrackFileFormat_ok() {
|
||||||
// given
|
// given
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
|
||||||
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.name());
|
editor.putString(context.getString(R.string.export_trackfileformat_key), TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.name());
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
assertEquals(TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||||
@@ -38,13 +45,13 @@ public class PreferencesUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void ExportTrackFileFormat_invalid() {
|
public void ExportTrackFileFormat_invalid() {
|
||||||
// given
|
// given
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
|
editor.putString(context.getString(R.string.export_trackfileformat_key), "invalid");
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||||
@@ -53,15 +60,142 @@ public class PreferencesUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void ExportTrackFileFormat_noValue() {
|
public void ExportTrackFileFormat_noValue() {
|
||||||
// given
|
// given
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
editor.clear();
|
editor.clear();
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(sharedPreferences, context);
|
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetCustomLayout_default() {
|
||||||
|
// given
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
editor.clear();
|
||||||
|
editor.commit();
|
||||||
|
|
||||||
|
// when
|
||||||
|
Layout layout = PreferencesUtils.getCustomLayout();
|
||||||
|
|
||||||
|
// 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 = PreferenceManager.getDefaultSharedPreferences(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();
|
||||||
|
|
||||||
|
// 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 = PreferenceManager.getDefaultSharedPreferences(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();
|
||||||
|
|
||||||
|
// 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 = PreferenceManager.getDefaultSharedPreferences(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(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();
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@@ -31,6 +30,7 @@ import java.util.Locale;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@@ -47,14 +47,12 @@ public class TrackNameUtilsTest {
|
|||||||
|
|
||||||
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
private static final SharedPreferences SHARED_PREFERENCES = PreferencesUtils.getSharedPreferences(CONTEXT);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
|
* Tests when the track_name_key is settings_recording_track_name_date_local_value.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTrackName_date_local() {
|
public void testTrackName_date_local() {
|
||||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
|
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_local_value));
|
||||||
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
assertEquals(StringUtils.formatDateTime(CONTEXT, START_TIME), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +61,7 @@ public class TrackNameUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTrackName_date_iso_8601() {
|
public void testTrackName_date_iso_8601() {
|
||||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
|
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_date_iso_8601_value));
|
||||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
|
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TrackNameUtils.ISO_8601_FORMAT, Locale.US);
|
||||||
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
assertEquals(simpleDateFormat.format(START_TIME.toEpochMilli()), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||||
}
|
}
|
||||||
@@ -73,7 +71,7 @@ public class TrackNameUtilsTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTrackName_number() {
|
public void testTrackName_number() {
|
||||||
PreferencesUtils.setString(SHARED_PREFERENCES, CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
|
PreferencesUtils.setString(R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
|
||||||
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
|||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class IntervalStatisticsTest {
|
public class IntervalStatisticsTest {
|
||||||
@@ -115,9 +116,40 @@ public class IntervalStatisticsTest {
|
|||||||
whenAndThen(10000, distanceInterval);
|
whenAndThen(10000, distanceInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithNoLossTrackPoints() {
|
||||||
|
// TrackPoints with elevation gain but without elevation loss.
|
||||||
|
|
||||||
|
// given
|
||||||
|
float distanceInterval = 1000f;
|
||||||
|
int numberOfPoints = 10000;
|
||||||
|
Track dummyTrack = new Track();
|
||||||
|
dummyTrack.setId(new Track.Id(System.currentTimeMillis()));
|
||||||
|
dummyTrack.setName("Dummy Track Without Elevation Loss");
|
||||||
|
contentProviderUtils.insertTrack(dummyTrack);
|
||||||
|
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||||
|
for (int i = 0; i < numberOfPoints; i++) {
|
||||||
|
TrackPoint tp = TestDataUtil.createTrackPoint(i);
|
||||||
|
tp.setAltitudeLoss(null);
|
||||||
|
contentProviderUtils.insertTrackPoint(tp, dummyTrack.getId());
|
||||||
|
trackStatisticsUpdater.addTrackPoint(tp, Distance.of(0));
|
||||||
|
}
|
||||||
|
dummyTrack.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||||
|
contentProviderUtils.updateTrack(dummyTrack);
|
||||||
|
Pair<Track.Id, TrackStatistics> trackWithStats = new Pair<>(dummyTrack.getId(), trackStatisticsUpdater.getTrackStatistics());
|
||||||
|
|
||||||
|
// when and then
|
||||||
|
whenAndThen(trackWithStats, numberOfPoints, distanceInterval);
|
||||||
|
}
|
||||||
|
|
||||||
private void whenAndThen(int numberOfPoints, float distanceInterval) {
|
private void whenAndThen(int numberOfPoints, float distanceInterval) {
|
||||||
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval), Distance.of(0));
|
|
||||||
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
Pair<Track.Id, TrackStatistics> trackWithStats = TestDataUtil.buildTrackWithTrackPoints(contentProviderUtils, numberOfPoints);
|
||||||
|
whenAndThen(trackWithStats, numberOfPoints, distanceInterval);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void whenAndThen(Pair<Track.Id, TrackStatistics> trackWithStats, int numberOfPoints, float distanceInterval) {
|
||||||
|
IntervalStatistics intervalStatistics = new IntervalStatistics(Distance.of(distanceInterval), Distance.of(0));
|
||||||
Track.Id trackId = trackWithStats.first;
|
Track.Id trackId = trackWithStats.first;
|
||||||
TrackStatistics trackStatistics = trackWithStats.second;
|
TrackStatistics trackStatistics = trackWithStats.second;
|
||||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(trackId, null)) {
|
||||||
@@ -127,18 +159,39 @@ public class IntervalStatisticsTest {
|
|||||||
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
|
List<IntervalStatistics.Interval> intervalList = intervalStatistics.getIntervalList();
|
||||||
Distance totalDistance = Distance.of(0);
|
Distance totalDistance = Distance.of(0);
|
||||||
float totalTime = 0L;
|
float totalTime = 0L;
|
||||||
float totalGain = 0f;
|
Float totalGain = null;
|
||||||
|
Float totalLoss = null;
|
||||||
for (IntervalStatistics.Interval i : intervalList) {
|
for (IntervalStatistics.Interval i : intervalList) {
|
||||||
totalDistance = totalDistance.plus(i.getDistance());
|
totalDistance = totalDistance.plus(i.getDistance());
|
||||||
totalTime += i.getDistance().toM() / i.getSpeed().toMPS();
|
totalTime += i.getDistance().toM() / i.getSpeed().toMPS();
|
||||||
|
|
||||||
|
if (totalGain == null) {
|
||||||
|
totalGain = i.getGain_m();
|
||||||
|
} else if (i.getGain_m() != null) {
|
||||||
totalGain += i.getGain_m();
|
totalGain += i.getGain_m();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (totalLoss == null) {
|
||||||
|
totalLoss = i.getLoss_m();
|
||||||
|
} else if (i.getLoss_m() != null) {
|
||||||
|
totalLoss += i.getLoss_m();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance.toM(), 0.01);
|
assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance.toM(), 0.01);
|
||||||
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1);
|
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1);
|
||||||
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval));
|
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval));
|
||||||
|
if (totalGain != null) {
|
||||||
assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1);
|
assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1);
|
||||||
|
} else {
|
||||||
|
assertTrue(intervalStatistics.getIntervalList().stream().noneMatch(IntervalStatistics.Interval::hasGain));
|
||||||
|
}
|
||||||
|
if (totalLoss != null) {
|
||||||
|
assertEquals(totalLoss, numberOfPoints * TestDataUtil.ALTITUDE_LOSS, 0.1);
|
||||||
|
} else {
|
||||||
|
assertTrue(intervalStatistics.getIntervalList().stream().noneMatch(IntervalStatistics.Interval::hasLoss));
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < intervalList.size() - 1; i++) {
|
for (int i = 0; i < intervalList.size() - 1; i++) {
|
||||||
assertEquals(intervalList.get(i).getDistance().toM(), distanceInterval, 0.001);
|
assertEquals(intervalList.get(i).getDistance().toM(), distanceInterval, 0.001);
|
||||||
|
|||||||
@@ -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))));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||||
|
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||||
|
<trk>
|
||||||
|
<name>20210907_213924.gpx</name>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:10:19Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
<speed>5</speed>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14184657" lon="-40.38670089">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:11:07Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
<speed>4</speed>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:12:00Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
<speed>3</speed>
|
||||||
|
</trkpt>
|
||||||
|
</trkseg>
|
||||||
|
</trk>
|
||||||
|
</gpx>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||||
|
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||||
|
<trk>
|
||||||
|
<name>20210907_213924.gpx</name>
|
||||||
|
<trkseg>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:10:19Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14184657" lon="-40.38670089">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:11:07Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
</trkpt>
|
||||||
|
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||||
|
<ele>-5</ele>
|
||||||
|
<time>2021-09-07T22:12:00Z</time>
|
||||||
|
<hdop>4</hdop>
|
||||||
|
</trkpt>
|
||||||
|
</trkseg>
|
||||||
|
</trk>
|
||||||
|
</gpx>
|
||||||
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="de.dennisguse.opentracks"
|
package="de.dennisguse.opentracks"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
||||||
@@ -52,7 +53,10 @@ limitations under the License.
|
|||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||||
android:maxSdkVersion="30" />
|
android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
<uses-permission
|
||||||
|
android:name="android.permission.BLUETOOTH_SCAN"
|
||||||
|
android:usesPermissionFlags="neverForLocation"
|
||||||
|
tools:targetApi="s" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
@@ -304,7 +308,11 @@ limitations under the License.
|
|||||||
|
|
||||||
<activity android:name=".TrackRecordedActivity" />
|
<activity android:name=".TrackRecordedActivity" />
|
||||||
|
|
||||||
<activity android:name=".TrackRecordingActivity" />
|
<activity android:name=".TrackRecordingActivity" android:parentActivityName=".TrackListActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value=".TrackListActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".TrackEditActivity" />
|
<activity android:name=".TrackEditActivity" />
|
||||||
|
|
||||||
@@ -316,6 +324,7 @@ limitations under the License.
|
|||||||
<activity android:name=".io.file.exporter.ExportActivity" />
|
<activity android:name=".io.file.exporter.ExportActivity" />
|
||||||
|
|
||||||
<activity android:name=".settings.SettingsActivity" />
|
<activity android:name=".settings.SettingsActivity" />
|
||||||
|
<activity android:name=".settings.SettingsCustomLayoutActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ShowOnMapProxyActivity$KML"
|
android:name=".ShowOnMapProxyActivity$KML"
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ import android.view.View;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import de.dennisguse.opentracks.services.tasks.VoiceAnnouncement;
|
||||||
import de.dennisguse.opentracks.util.TTSUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
@@ -35,7 +34,7 @@ public abstract class AbstractActivity extends AppCompatActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Set volume control stream for text to speech
|
// Set volume control stream for text to speech
|
||||||
setVolumeControlStream(TTSUtils.getTTSStream());
|
setVolumeControlStream(VoiceAnnouncement.AUDIO_STREAM);
|
||||||
|
|
||||||
setContentView(getRootView());
|
setContentView(getRootView());
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Code that is executed when the application starts.
|
* Code that is executed when the application starts.
|
||||||
@@ -27,13 +25,14 @@ public class Startup extends Application {
|
|||||||
// Include version information into stack traces.
|
// Include version information into stack traces.
|
||||||
Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE);
|
Log.i(TAG, BuildConfig.APPLICATION_ID + "; BuildType: " + BuildConfig.BUILD_TYPE + "; VersionName: " + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_NAME_FULL + " VersionCode: " + BuildConfig.VERSION_CODE);
|
||||||
|
|
||||||
|
PreferencesUtils.initPreferences(this, getResources());
|
||||||
// Set default values of preferences on first start.
|
// Set default values of preferences on first start.
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.resetPreferences(this, false);
|
PreferencesUtils.resetPreferences(this, false);
|
||||||
if (PreferencesUtils.getString(sharedPreferences, this, R.string.stats_units_key, "").equals("")) {
|
if (PreferencesUtils.getString( R.string.stats_units_key, "").equals("")) {
|
||||||
PreferencesUtils.setMetricUnits(sharedPreferences, this, !Locale.US.equals(Locale.getDefault()));
|
PreferencesUtils.setMetricUnits(!Locale.US.equals(Locale.getDefault()));
|
||||||
}
|
}
|
||||||
|
PreferencesUtils.applyNightMode();
|
||||||
|
|
||||||
ActivityUtils.applyNightMode(sharedPreferences, this);
|
|
||||||
|
|
||||||
// In debug builds: show thread and VM warnings.
|
// In debug builds: show thread and VM warnings.
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
@@ -41,4 +40,13 @@ public class Startup extends Application {
|
|||||||
StrictMode.enableDefaults();
|
StrictMode.enableDefaults();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the database used by SQLiteOpenHelper.
|
||||||
|
* See {@link android.database.sqlite.SQLiteOpenHelper} for details.
|
||||||
|
* @return SQLite database name.
|
||||||
|
*/
|
||||||
|
public String getDatabaseName() {
|
||||||
|
return "database.db";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import android.app.ActivityOptions;
|
|||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
@@ -64,7 +63,7 @@ import de.dennisguse.opentracks.util.ActivityUtils;
|
|||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
@@ -77,8 +76,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
|
|
||||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||||
|
|
||||||
// The following are set in onCreate
|
// The following are setFrequency in onCreate
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
private ResourceCursorAdapter resourceCursorAdapter;
|
private ResourceCursorAdapter resourceCursorAdapter;
|
||||||
|
|
||||||
@@ -110,8 +108,8 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
};
|
};
|
||||||
|
|
||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||||
metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, TrackListActivity.this);
|
metricUnits = PreferencesUtils.isMetricUnits();
|
||||||
}
|
}
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
@@ -161,8 +159,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||||
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
|
||||||
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
|
|
||||||
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
viewBinding.trackList.setEmptyView(viewBinding.trackListEmptyView);
|
||||||
@@ -231,8 +227,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
|
||||||
trackRecordingServiceConnection.startConnection(this);
|
trackRecordingServiceConnection.startConnection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +244,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
trackRecordingServiceConnection.unbind(this);
|
trackRecordingServiceConnection.unbind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
|||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to show the track detail, record a new track or resumes an existing one.
|
* An activity to show the track detail, record a new track or resumes an existing one.
|
||||||
@@ -66,9 +66,8 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
|
|
||||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||||
|
|
||||||
// The following are set in onCreate.
|
// The following are setFrequency in onCreate.
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
private TrackDataHub trackDataHub;
|
private TrackDataHub trackDataHub;
|
||||||
|
|
||||||
private TrackRecordedBinding viewBinding;
|
private TrackRecordedBinding viewBinding;
|
||||||
@@ -96,8 +95,8 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
if (PreferencesUtils.isKey(TrackRecordedActivity.this, R.string.recording_distance_interval_key, key)) {
|
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
|
||||||
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordedActivity.this));
|
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -107,7 +106,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
contentProviderUtils = new ContentProviderUtils(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
|
||||||
|
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
|
|
||||||
@@ -130,8 +128,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
|
||||||
|
|
||||||
trackDataHub.start();
|
trackDataHub.start();
|
||||||
}
|
}
|
||||||
@@ -155,7 +152,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
|||||||
super.onStop();
|
super.onStop();
|
||||||
trackRecordingServiceConnection.unbind(this);
|
trackRecordingServiceConnection.unbind(this);
|
||||||
trackDataHub.stop();
|
trackDataHub.stop();
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
|||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackUtils;
|
import de.dennisguse.opentracks.util.TrackUtils;
|
||||||
|
|
||||||
@@ -51,9 +51,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
|
|
||||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||||
|
|
||||||
// The following are set in onCreate
|
// The following are setFrequency in onCreate
|
||||||
private ContentProviderUtils contentProviderUtils;
|
private ContentProviderUtils contentProviderUtils;
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
private TrackDataHub trackDataHub;
|
private TrackDataHub trackDataHub;
|
||||||
|
|
||||||
@@ -96,17 +95,17 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_show_on_lockscreen_while_recording_key, key)) {
|
||||||
setLockscreenPolicy();
|
setLockscreenPolicy();
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_keep_screen_on_while_recording_key, key)) {
|
||||||
setScreenOnPolicy();
|
setScreenOnPolicy();
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_fullscreen_while_recording_key, key)) {
|
||||||
setFullscreenPolicy();
|
setFullscreenPolicy();
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.recording_distance_interval_key, key)) {
|
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
|
||||||
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, TrackRecordingActivity.this));
|
trackDataHub.setRecordingDistanceInterval(PreferencesUtils.getRecordingDistanceInterval());
|
||||||
}
|
}
|
||||||
if (key == null) return;
|
if (key == null) return;
|
||||||
|
|
||||||
@@ -121,7 +120,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
contentProviderUtils = new ContentProviderUtils(this);
|
contentProviderUtils = new ContentProviderUtils(this);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
|
||||||
trackId = null;
|
trackId = null;
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
//Activity was recreated.
|
//Activity was recreated.
|
||||||
@@ -156,7 +154,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setLockscreenPolicy() {
|
private void setLockscreenPolicy() {
|
||||||
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(sharedPreferences, TrackRecordingActivity.this);
|
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
setShowWhenLocked(showOnLockScreen);
|
setShowWhenLocked(showOnLockScreen);
|
||||||
@@ -168,7 +166,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setScreenOnPolicy() {
|
private void setScreenOnPolicy() {
|
||||||
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(sharedPreferences, TrackRecordingActivity.this);
|
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn();
|
||||||
|
|
||||||
if (keepScreenOn) {
|
if (keepScreenOn) {
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
@@ -178,7 +176,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setFullscreenPolicy() {
|
private void setFullscreenPolicy() {
|
||||||
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(sharedPreferences, TrackRecordingActivity.this);
|
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen();
|
||||||
|
|
||||||
if (keepScreenOn) {
|
if (keepScreenOn) {
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
@@ -191,8 +189,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
|
||||||
|
|
||||||
trackRecordingServiceConnection.startConnection(this);
|
trackRecordingServiceConnection.startConnection(this);
|
||||||
trackDataHub.start();
|
trackDataHub.start();
|
||||||
@@ -230,7 +227,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
trackRecordingServiceConnection.unbind(this);
|
trackRecordingServiceConnection.unbind(this);
|
||||||
trackDataHub.stop();
|
trackDataHub.stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package de.dennisguse.opentracks.adapters;
|
package de.dennisguse.opentracks.adapters;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -11,7 +10,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
|
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
|
||||||
@@ -58,7 +57,7 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
|||||||
viewHolder = (ViewHolder) convertView.getTag();
|
viewHolder = (ViewHolder) convertView.getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TrackIconUtils.isSpeedIcon(context, aggregatedStatistic.getCategory())) {
|
if (TrackIconUtils.isSpeedIcon(context.getResources(), aggregatedStatistic.getCategory())) {
|
||||||
viewHolder.setSpeed(aggregatedStatistic);
|
viewHolder.setSpeed(aggregatedStatistic);
|
||||||
} else {
|
} else {
|
||||||
viewHolder.setPace(aggregatedStatistic);
|
viewHolder.setPace(aggregatedStatistic);
|
||||||
@@ -139,9 +138,8 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
|
|||||||
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||||
String category = aggregatedStatistic.getCategory();
|
String category = aggregatedStatistic.getCategory();
|
||||||
|
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
reportSpeed = PreferencesUtils.isReportSpeed(category);
|
||||||
reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, context, category);
|
metricsUnits = PreferencesUtils.isMetricUnits();
|
||||||
metricsUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
|
|
||||||
|
|
||||||
sportIcon.setImageResource(getIcon(aggregatedStatistic));
|
sportIcon.setImageResource(getIcon(aggregatedStatistic));
|
||||||
typeLabel.setText(category);
|
typeLabel.setText(category);
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView
|
|||||||
|
|
||||||
viewHolder.rate.setText(StringUtils.formatSpeed(context, interval.getSpeed(), metricUnits, isReportSpeed));
|
viewHolder.rate.setText(StringUtils.formatSpeed(context, interval.getSpeed(), metricUnits, isReportSpeed));
|
||||||
|
|
||||||
viewHolder.gain.setText(StringUtils.formatAltitude(context, interval.getGain_m(), metricUnits));
|
viewHolder.gain.setText(StringUtils.formatAltitudeChange(context, interval.getGain_m(), metricUnits));
|
||||||
viewHolder.loss.setText(StringUtils.formatAltitude(context, interval.getLoss_m(), metricUnits));
|
viewHolder.loss.setText(StringUtils.formatAltitudeChange(context, interval.getLoss_m(), metricUnits));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
|
||||||
private final TextView label;
|
|
||||||
private final TextView sensorValue;
|
|
||||||
private final TextView value;
|
|
||||||
private 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,113 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isItemWide(int position) {
|
||||||
|
return layout.getFields().get(position).isWide();
|
||||||
|
}
|
||||||
|
|
||||||
|
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,176 @@
|
|||||||
|
package de.dennisguse.opentracks.adapters;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextClock;
|
||||||
|
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.content.data.CustomLayoutFieldType;
|
||||||
|
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;
|
||||||
|
if (viewType == CustomLayoutFieldType.CLOCK.value()) {
|
||||||
|
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_clock_item, parent, false);
|
||||||
|
return new StatisticsAdapter.ViewClockHolder(view);
|
||||||
|
} else {
|
||||||
|
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_generic_item, parent, false);
|
||||||
|
return new StatisticsAdapter.ViewHolder(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||||
|
StatisticData statisticData = statisticDataList.get(position);
|
||||||
|
if (getItemViewType(position) == CustomLayoutFieldType.CLOCK.value()) {
|
||||||
|
StatisticsAdapter.ViewClockHolder viewHolder = (StatisticsAdapter.ViewClockHolder) holder;
|
||||||
|
viewHolder.setData(statisticData);
|
||||||
|
} else {
|
||||||
|
StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder;
|
||||||
|
viewHolder.setData(statisticData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
if (statisticDataList == null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return statisticDataList.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (statisticDataList.get(position).getField().getType(context) == CustomLayoutFieldType.CLOCK) {
|
||||||
|
return CustomLayoutFieldType.CLOCK.value();
|
||||||
|
} else {
|
||||||
|
return CustomLayoutFieldType.GENERIC.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isItemWide(int position) {
|
||||||
|
return statisticDataList.get(position).getField().isWide();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ViewClockHolder extends RecyclerView.ViewHolder {
|
||||||
|
final TextClock value;
|
||||||
|
final TextView descMain;
|
||||||
|
|
||||||
|
public ViewClockHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
value = itemView.findViewById(R.id.stats_clock);
|
||||||
|
descMain = itemView.findViewById(R.id.stats_description_main);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(StatisticData statisticData) {
|
||||||
|
if (statisticData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryValue : R.style.StatsSecondaryValue);
|
||||||
|
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,8 +41,8 @@ public class ChartPoint {
|
|||||||
if (trackPoint.hasHeartRate()) {
|
if (trackPoint.hasHeartRate()) {
|
||||||
heartRate = (double) trackPoint.getHeartRate_bpm();
|
heartRate = (double) trackPoint.getHeartRate_bpm();
|
||||||
}
|
}
|
||||||
if (trackPoint.hasCyclingCadence()) {
|
if (trackPoint.hasCadence()) {
|
||||||
cadence = (double) trackPoint.getCyclingCadence_rpm();
|
cadence = (double) trackPoint.getCadence_rpm();
|
||||||
}
|
}
|
||||||
if (trackPoint.hasPower()) {
|
if (trackPoint.hasPower()) {
|
||||||
power = (double) trackPoint.getPower();
|
power = (double) trackPoint.getPower();
|
||||||
@@ -77,6 +77,7 @@ public class ChartPoint {
|
|||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ChartPoint{" + "timeOrDistance=" + timeOrDistance + '}';
|
return "ChartPoint{" + "timeOrDistance=" + timeOrDistance + '}';
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package de.dennisguse.opentracks.content.data;
|
||||||
|
|
||||||
|
public enum CustomLayoutFieldType {
|
||||||
|
GENERIC(1),
|
||||||
|
CLOCK(2);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
CustomLayoutFieldType(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int value() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package de.dennisguse.opentracks.content.data;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
|
||||||
|
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 CustomLayoutFieldType getType(Context context) {
|
||||||
|
if (key.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
|
||||||
|
return CustomLayoutFieldType.CLOCK;
|
||||||
|
} else {
|
||||||
|
return CustomLayoutFieldType.GENERIC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(Resources resources, String key) {
|
||||||
|
if (key.equals(resources.getString(R.string.stats_custom_layout_total_time_key))) {
|
||||||
|
return resources.getString(R.string.stats_total_time);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_moving_time_key))) {
|
||||||
|
return resources.getString(R.string.stats_moving_time);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_distance_key))) {
|
||||||
|
return resources.getString(R.string.stats_distance);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_speed_key))) {
|
||||||
|
return resources.getString(R.string.stats_speed);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_pace_key))) {
|
||||||
|
return resources.getString(R.string.stats_pace);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
|
||||||
|
return resources.getString(R.string.stats_average_moving_speed);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_speed_key))) {
|
||||||
|
return resources.getString(R.string.stats_average_speed);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_max_speed_key))) {
|
||||||
|
return resources.getString(R.string.stats_max_speed);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
|
||||||
|
return resources.getString(R.string.stats_average_moving_pace);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_pace_key))) {
|
||||||
|
return resources.getString(R.string.stats_average_pace);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_fastest_pace_key))) {
|
||||||
|
return resources.getString(R.string.stats_fastest_pace);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_altitude_key))) {
|
||||||
|
return resources.getString(R.string.stats_altitude);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_gain_key))) {
|
||||||
|
return resources.getString(R.string.stats_gain);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_loss_key))) {
|
||||||
|
return resources.getString(R.string.stats_loss);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_coordinates_key))) {
|
||||||
|
return resources.getString(R.string.stats_coordinates);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_heart_rate_key))) {
|
||||||
|
return resources.getString(R.string.stats_sensors_heart_rate);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_cadence_key))) {
|
||||||
|
return resources.getString(R.string.stats_sensors_cadence);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_power_key))) {
|
||||||
|
return resources.getString(R.string.stats_sensors_power);
|
||||||
|
} else if (key.equals(resources.getString(R.string.stats_custom_layout_clock_key))) {
|
||||||
|
return resources.getString(R.string.stats_clock);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("It doesn't exists a field with key: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -123,6 +123,7 @@ public class Distance {
|
|||||||
return Objects.hash(distance_m);
|
return Objects.hash(distance_m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Distance{" +
|
return "Distance{" +
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
package de.dennisguse.opentracks.content.data;
|
package de.dennisguse.opentracks.content.data;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
public class Speed {
|
public class Speed {
|
||||||
@@ -23,6 +26,10 @@ public class Speed {
|
|||||||
return of(Float.parseFloat(speed_mps));
|
return of(Float.parseFloat(speed_mps));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Speed ofKMH(double speed_kmh) {
|
||||||
|
return of(Distance.ofKilometer(speed_kmh), Duration.ofHours(1));
|
||||||
|
}
|
||||||
|
|
||||||
public static Speed zero() {
|
public static Speed zero() {
|
||||||
return of(0.0);
|
return of(0.0);
|
||||||
}
|
}
|
||||||
@@ -40,10 +47,6 @@ public class Speed {
|
|||||||
return Speed.of(Math.abs(speed1.speed_mps - speed2.speed_mps));
|
return Speed.of(Math.abs(speed1.speed_mps - speed2.speed_mps));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Anything faster than that (in meters per second) will be considered moving.
|
|
||||||
private static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
|
||||||
|
|
||||||
private final double speed_mps;
|
private final double speed_mps;
|
||||||
|
|
||||||
private Speed(double speed_mps) {
|
private Speed(double speed_mps) {
|
||||||
@@ -63,7 +66,7 @@ public class Speed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMoving() {
|
public boolean isMoving() {
|
||||||
return !isInvalid() && speed_mps >= MAX_NO_MOVEMENT_SPEED;
|
return !isInvalid() && greaterThan(PreferencesUtils.getIdleSpeed());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean lessThan(Speed speed) {
|
public boolean lessThan(Speed speed) {
|
||||||
@@ -132,6 +135,7 @@ public class Speed {
|
|||||||
return Objects.hash(speed_mps);
|
return Objects.hash(speed_mps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Speed{" +
|
return "Speed{" +
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class TrackPoint {
|
|||||||
private Type type;
|
private Type type;
|
||||||
|
|
||||||
private Float heartRate_bpm = null;
|
private Float heartRate_bpm = null;
|
||||||
private Float cyclingCadence_rpm = null;
|
private Float cadence_rpm = null;
|
||||||
private Float power = null;
|
private Float power = null;
|
||||||
private Float altitudeGain_m = null;
|
private Float altitudeGain_m = null;
|
||||||
private Float altitudeLoss_m = null;
|
private Float altitudeLoss_m = null;
|
||||||
@@ -310,11 +310,11 @@ public class TrackPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
|
public Distance distanceToPrevious(TrackPoint previous) {
|
||||||
if (hasSensorDistance()) {
|
if (hasSensorDistance()) {
|
||||||
return getSensorDistance();
|
return getSensorDistance();
|
||||||
}
|
}
|
||||||
if (!(hasLocation() && previous.hasLocation())) {
|
if (previous == null || !(hasLocation() && previous.hasLocation())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +350,7 @@ public class TrackPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasSensorData() {
|
public boolean hasSensorData() {
|
||||||
return hasHeartRate() || hasCyclingCadence() || hasPower();
|
return hasHeartRate() || hasCadence() || hasPower();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasHeartRate() {
|
public boolean hasHeartRate() {
|
||||||
@@ -366,16 +366,16 @@ public class TrackPoint {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasCyclingCadence() {
|
public boolean hasCadence() {
|
||||||
return cyclingCadence_rpm != null;
|
return cadence_rpm != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getCyclingCadence_rpm() {
|
public float getCadence_rpm() {
|
||||||
return cyclingCadence_rpm;
|
return cadence_rpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackPoint setCyclingCadence_rpm(Float cyclingCadence_rpm) {
|
public TrackPoint setCadence_rpm(Float cadence_rpm) {
|
||||||
this.cyclingCadence_rpm = cyclingCadence_rpm;
|
this.cadence_rpm = cadence_rpm;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,15 +396,17 @@ public class TrackPoint {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String result = "time=" + getTime() + " (type=" + getType() + ")";
|
String result = "time=" + getTime() + " (type=" + getType() + ")";
|
||||||
if (!hasLocation()) {
|
if (hasLocation()) {
|
||||||
return result;
|
|
||||||
}
|
|
||||||
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
|
result += ": lat=" + getLatitude() + " lng=" + getLongitude();
|
||||||
if (!hasHorizontalAccuracy()) {
|
}
|
||||||
return result;
|
if (hasHorizontalAccuracy()) {
|
||||||
|
result += " acc=" + getHorizontalAccuracy();
|
||||||
|
}
|
||||||
|
if (hasSensorDistance()) {
|
||||||
|
result += " distance=" + getSensorDistance();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result + " acc=" + getHorizontalAccuracy();
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Id {
|
public static class Id {
|
||||||
|
|||||||
@@ -290,6 +290,31 @@ public class ContentProviderUtils {
|
|||||||
return values;
|
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) {
|
public Marker createMarker(Cursor cursor) {
|
||||||
int idIndex = cursor.getColumnIndexOrThrow(MarkerColumns._ID);
|
int idIndex = cursor.getColumnIndexOrThrow(MarkerColumns._ID);
|
||||||
int nameIndex = cursor.getColumnIndexOrThrow(MarkerColumns.NAME);
|
int nameIndex = cursor.getColumnIndexOrThrow(MarkerColumns.NAME);
|
||||||
@@ -533,7 +558,7 @@ public class ContentProviderUtils {
|
|||||||
trackPoint.setHeartRate_bpm(cursor.getFloat(indexes.sensorHeartRateIndex));
|
trackPoint.setHeartRate_bpm(cursor.getFloat(indexes.sensorHeartRateIndex));
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
|
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
|
||||||
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
|
trackPoint.setCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
|
||||||
}
|
}
|
||||||
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
|
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
|
||||||
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(indexes.sensorDistanceIndex)));
|
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(indexes.sensorDistanceIndex)));
|
||||||
@@ -694,8 +719,8 @@ public class ContentProviderUtils {
|
|||||||
if (trackPoint.hasHeartRate()) {
|
if (trackPoint.hasHeartRate()) {
|
||||||
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getHeartRate_bpm());
|
values.put(TrackPointsColumns.SENSOR_HEARTRATE, trackPoint.getHeartRate_bpm());
|
||||||
}
|
}
|
||||||
if (trackPoint.hasCyclingCadence()) {
|
if (trackPoint.hasCadence()) {
|
||||||
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
|
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCadence_rpm());
|
||||||
}
|
}
|
||||||
if (trackPoint.hasSensorDistance()) {
|
if (trackPoint.hasSensorDistance()) {
|
||||||
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance().toM());
|
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance().toM());
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.Startup;
|
||||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||||
@@ -27,11 +28,8 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
private static final int DATABASE_VERSION = 31;
|
private static final int DATABASE_VERSION = 31;
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public static final String DATABASE_NAME = "database.db";
|
|
||||||
|
|
||||||
public CustomSQLiteOpenHelper(Context context) {
|
public CustomSQLiteOpenHelper(Context context) {
|
||||||
this(context, DATABASE_NAME);
|
this(context, ((Startup) context.getApplicationContext()).getDatabaseName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -50,9 +50,18 @@ public abstract class SensorData<T> {
|
|||||||
return value != null;
|
return value != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
protected abstract T getNoneValue();
|
||||||
|
|
||||||
public T getValue() {
|
public T getValue() {
|
||||||
|
if (!hasValue()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isRecent()) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
return getNoneValue();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset long term aggregated values (more than derived from previous SensorData). e.g. overall distance.
|
* Reset long term aggregated values (more than derived from previous SensorData). e.g. overall distance.
|
||||||
@@ -63,7 +72,7 @@ public abstract class SensorData<T> {
|
|||||||
/**
|
/**
|
||||||
* Is the data recent considering the current time.
|
* Is the data recent considering the current time.
|
||||||
*/
|
*/
|
||||||
public boolean isRecent() {
|
private boolean isRecent() {
|
||||||
return Instant.now()
|
return Instant.now()
|
||||||
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS));
|
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,19 +59,32 @@ public final class SensorDataCycling {
|
|||||||
return crankRevolutionsTime;
|
return crankRevolutionsTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Float getNoneValue() {
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
public void compute(Cadence previous) {
|
public void compute(Cadence previous) {
|
||||||
if (hasData() && previous != null && previous.hasData()) {
|
if (hasData() && previous != null && previous.hasData()) {
|
||||||
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||||
if (timeDiff_ms <= 0) {
|
if (timeDiff_ms <= 0) {
|
||||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||||
value = null;
|
value = null;
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO We have to treat with overflow according to the documentation: read https://github.com/OpenTracksApp/OpenTracks/pull/953#discussion_r711625268
|
||||||
|
if (crankRevolutionsCount < previous.crankRevolutionsCount) {
|
||||||
|
Log.e(TAG, "Crank revolutions count difference is invalid: cannot compute cadence.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
|
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
|
||||||
float cadence_ms = crankDiff / timeDiff_ms;
|
float cadence_ms = crankDiff / timeDiff_ms;
|
||||||
value = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
|
value = (float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
@@ -121,6 +134,16 @@ public final class SensorDataCycling {
|
|||||||
return wheelRevolutionsTime;
|
return wheelRevolutionsTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Data getNoneValue() {
|
||||||
|
if (value != null) {
|
||||||
|
return new Data(value.distance, value.distanceOverall, Speed.zero());
|
||||||
|
} else {
|
||||||
|
return new Data(Distance.of(0), Distance.of(0), Speed.zero());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void compute(DistanceSpeed previous, Distance wheelCircumference) {
|
public void compute(DistanceSpeed previous, Distance wheelCircumference) {
|
||||||
if (hasData() && previous != null && previous.hasData()) {
|
if (hasData() && previous != null && previous.hasData()) {
|
||||||
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||||
@@ -131,6 +154,10 @@ public final class SensorDataCycling {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wheelRevolutionsCount < previous.wheelRevolutionsCount) {
|
||||||
|
Log.e(TAG, "Wheel revolutions count difference is invalid: cannot compute speed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT32_MAX);
|
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT32_MAX);
|
||||||
|
|
||||||
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
|
Distance distance = wheelCircumference.multipliedBy(wheelDiff);
|
||||||
@@ -191,6 +218,7 @@ public final class SensorDataCycling {
|
|||||||
return speed;
|
return speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Data{" +
|
return "Data{" +
|
||||||
@@ -216,6 +244,12 @@ public final class SensorDataCycling {
|
|||||||
public DistanceSpeed getDistanceSpeed() {
|
public DistanceSpeed getDistanceSpeed() {
|
||||||
return this.value != null ? this.value.second : null;
|
return this.value != null ? this.value.second : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Pair<Cadence, DistanceSpeed> getNoneValue() {
|
||||||
|
return new Pair<>(null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,10 @@ public class SensorDataCyclingPower extends SensorData<Float> {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return super.toString() + " power=" + value;
|
return super.toString() + " power=" + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Float getNoneValue() {
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,10 @@ public class SensorDataHeartRate extends SensorData<Float> {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return super.toString() + " heart=" + value;
|
return super.toString() + " heart=" + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Float getNoneValue() {
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.dennisguse.opentracks.content.sensor;
|
package de.dennisguse.opentracks.content.sensor;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
@@ -42,7 +43,6 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
|
|||||||
return cadence;
|
return cadence;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public Speed getSpeed() {
|
public Speed getSpeed() {
|
||||||
return speed;
|
return speed;
|
||||||
}
|
}
|
||||||
@@ -52,9 +52,20 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
|
|||||||
return totalDistance;
|
return totalDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Data getNoneValue() {
|
||||||
|
if (value != null) {
|
||||||
|
return new Data(Speed.zero(), 0f, value.distance);
|
||||||
|
} else {
|
||||||
|
return new Data(Speed.zero(), 0f, Distance.of(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void compute(SensorDataRunning previous) {
|
public void compute(SensorDataRunning previous) {
|
||||||
|
if (speed != null && hasTotalDistance()) {
|
||||||
Distance overallDistance = null;
|
Distance overallDistance = null;
|
||||||
if (hasTotalDistance() && previous != null && previous.hasTotalDistance()) {
|
if (previous != null && previous.hasTotalDistance()) {
|
||||||
overallDistance = this.totalDistance.minus(previous.totalDistance);
|
overallDistance = this.totalDistance.minus(previous.totalDistance);
|
||||||
if (previous.hasValue() && previous.getValue().getDistance() != null) {
|
if (previous.hasValue() && previous.getValue().getDistance() != null) {
|
||||||
overallDistance = overallDistance.plus(previous.getValue().getDistance());
|
overallDistance = overallDistance.plus(previous.getValue().getDistance());
|
||||||
@@ -63,6 +74,7 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
|
|||||||
|
|
||||||
value = new Data(speed, cadence, overallDistance);
|
value = new Data(speed, cadence, overallDistance);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
@@ -92,10 +104,12 @@ public final class SensorDataRunning extends SensorData<SensorDataRunning.Data>
|
|||||||
return cadence;
|
return cadence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public Distance getDistance() {
|
public Distance getDistance() {
|
||||||
return distance;
|
return distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Data{" +
|
return "Data{" +
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package de.dennisguse.opentracks.content.sensor;
|
package de.dennisguse.opentracks.content.sensor;
|
||||||
|
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
|
||||||
public final class SensorDataSet {
|
public final class SensorDataSet {
|
||||||
@@ -27,8 +30,36 @@ public final class SensorDataSet {
|
|||||||
this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence;
|
this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SensorDataHeartRate getHeartRate() {
|
public Pair<Float, String> getHeartRate() {
|
||||||
return heartRate;
|
if (heartRate != null) {
|
||||||
|
return new Pair<>(heartRate.getValue(), heartRate.getSensorNameOrAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<Float, String> getCadence() {
|
||||||
|
if (cyclingCadence != null) {
|
||||||
|
return new Pair<>(cyclingCadence.getValue(), cyclingCadence.getSensorNameOrAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningDistanceSpeedCadence != null) {
|
||||||
|
return new Pair<>(runningDistanceSpeedCadence.getCadence(), runningDistanceSpeedCadence.getSensorNameOrAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<Speed, String> getSpeed() {
|
||||||
|
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue() && cyclingDistanceSpeed.getValue().getSpeed() != null) {
|
||||||
|
return new Pair<>(cyclingDistanceSpeed.getValue().getSpeed(), cyclingDistanceSpeed.getSensorNameOrAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue() && runningDistanceSpeedCadence.getValue().getSpeed() != null) {
|
||||||
|
return new Pair<>(runningDistanceSpeedCadence.getSpeed(), runningDistanceSpeedCadence.getSensorNameOrAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SensorDataCycling.Cadence getCyclingCadence() {
|
public SensorDataCycling.Cadence getCyclingCadence() {
|
||||||
@@ -64,17 +95,20 @@ public final class SensorDataSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void fillTrackPoint(TrackPoint trackPoint) {
|
public void fillTrackPoint(TrackPoint trackPoint) {
|
||||||
if (heartRate != null) {
|
if (getHeartRate() != null) {
|
||||||
trackPoint.setHeartRate_bpm(heartRate.getValue());
|
trackPoint.setHeartRate_bpm(getHeartRate().first);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cyclingCadence != null && cyclingCadence.hasValue()) {
|
if (getCadence() != null) {
|
||||||
trackPoint.setCyclingCadence_rpm(cyclingCadence.getValue());
|
trackPoint.setCadence_rpm(getCadence().first);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getSpeed() != null) {
|
||||||
|
trackPoint.setSpeed(getSpeed().first);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
|
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
|
||||||
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().getDistanceOverall());
|
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().getDistanceOverall());
|
||||||
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().getSpeed());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cyclingPower != null && cyclingPower.hasValue()) {
|
if (cyclingPower != null && cyclingPower.hasValue()) {
|
||||||
@@ -83,8 +117,6 @@ public final class SensorDataSet {
|
|||||||
|
|
||||||
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue()) {
|
if (runningDistanceSpeedCadence != null && runningDistanceSpeedCadence.hasValue()) {
|
||||||
trackPoint.setSensorDistance(runningDistanceSpeedCadence.getValue().getDistance());
|
trackPoint.setSensorDistance(runningDistanceSpeedCadence.getValue().getDistance());
|
||||||
trackPoint.setSpeed(runningDistanceSpeedCadence.getValue().getSpeed());
|
|
||||||
trackPoint.setCyclingCadence_rpm(runningDistanceSpeedCadence.getValue().getCadence());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,18 +125,17 @@ public final class SensorDataSet {
|
|||||||
if (cyclingCadence != null) cyclingCadence.reset();
|
if (cyclingCadence != null) cyclingCadence.reset();
|
||||||
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
|
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
|
||||||
if (cyclingPower != null) cyclingPower.reset();
|
if (cyclingPower != null) cyclingPower.reset();
|
||||||
|
|
||||||
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset();
|
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return (getHeartRate() != null ? "" + getHeartRate() : "")
|
return (heartRate != null ? "" + heartRate : "")
|
||||||
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
|
+ (cyclingCadence != null ? " " + cyclingCadence : "")
|
||||||
+ (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
|
+ (cyclingDistanceSpeed != null ? " " + cyclingDistanceSpeed : "")
|
||||||
+ (getCyclingPower() != null ? " " + getCyclingPower() : "")
|
+ (cyclingPower != null ? " " + cyclingPower : "")
|
||||||
+ (getRunningDistanceSpeedCadence() != null ? " " + getRunningDistanceSpeedCadence() : "");
|
+ (runningDistanceSpeedCadence != null ? " " + runningDistanceSpeedCadence : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set(@NonNull SensorData<?> type, SensorData<?> data) {
|
private void set(@NonNull SensorData<?> type, SensorData<?> data) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
|
|||||||
import de.dennisguse.opentracks.databinding.ChartBinding;
|
import de.dennisguse.opentracks.databinding.ChartBinding;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment to display track chart to the user.
|
* A fragment to display track chart to the user.
|
||||||
@@ -64,8 +64,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
return chartFragment;
|
return chartFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
private TrackDataHub trackDataHub;
|
private TrackDataHub trackDataHub;
|
||||||
|
|
||||||
// Stats gathered from the received data
|
// Stats gathered from the received data
|
||||||
@@ -80,8 +78,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
boolean metricUnits = PreferencesUtils.isMetricUnits();
|
||||||
if (metricUnits != viewBinding.chartView.getMetricUnits()) {
|
if (metricUnits != viewBinding.chartView.getMetricUnits()) {
|
||||||
viewBinding.chartView.setMetricUnits(metricUnits);
|
viewBinding.chartView.setMetricUnits(metricUnits);
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
@@ -91,8 +89,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_rate_key, key)) {
|
||||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), category);
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(category);
|
||||||
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
|
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
|
||||||
viewBinding.chartView.setReportSpeed(reportSpeed);
|
viewBinding.chartView.setReportSpeed(reportSpeed);
|
||||||
viewBinding.chartView.applyReportSpeed();
|
viewBinding.chartView.applyReportSpeed();
|
||||||
@@ -108,7 +106,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A runnable that will set the orange pointer as appropriate and redraw.
|
* A runnable that will setFrequency the orange pointer as appropriate and redraw.
|
||||||
*/
|
*/
|
||||||
private final Runnable updateChart = new Runnable() {
|
private final Runnable updateChart = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -141,9 +139,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
resumeTrackDataHub();
|
resumeTrackDataHub();
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
|
||||||
|
|
||||||
checkChartSettings();
|
checkChartSettings();
|
||||||
getActivity().runOnUiThread(updateChart);
|
getActivity().runOnUiThread(updateChart);
|
||||||
@@ -153,7 +150,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
pauseTrackDataHub();
|
pauseTrackDataHub();
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -162,12 +159,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
viewBinding = null;
|
viewBinding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
sharedPreferences = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackUpdated(Track track) {
|
public void onTrackUpdated(Track track) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@@ -177,7 +168,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
category = track.getCategory();
|
category = track.getCategory();
|
||||||
boolean reportSpeed = PreferencesUtils.isReportSpeed(PreferencesUtils.getSharedPreferences(getContext()), getContext(), category);
|
boolean reportSpeed = PreferencesUtils.isReportSpeed(category);
|
||||||
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
|
if (reportSpeed != viewBinding.chartView.getReportSpeed()) {
|
||||||
viewBinding.chartView.setReportSpeed(reportSpeed);
|
viewBinding.chartView.setReportSpeed(reportSpeed);
|
||||||
viewBinding.chartView.applyReportSpeed();
|
viewBinding.chartView.applyReportSpeed();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
|
|||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
|
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||||
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
||||||
|
|
||||||
@@ -46,15 +46,13 @@ public class IntervalsFragment extends Fragment {
|
|||||||
private IntervalStatisticsAdapter adapter;
|
private IntervalStatisticsAdapter adapter;
|
||||||
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
|
private ArrayAdapter<IntervalStatisticsModel.IntervalOption> spinnerAdapter;
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
private boolean isReportSpeed;
|
private boolean isReportSpeed;
|
||||||
|
|
||||||
private IntervalListViewBinding viewBinding;
|
private IntervalListViewBinding viewBinding;
|
||||||
|
|
||||||
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_units_key, key) || PreferencesUtils.isKey(R.string.stats_rate_key, key)) {
|
||||||
updateIntervals(PreferencesUtils.isMetricUnits(sharedPreferences, getContext()), selectedInterval);
|
updateIntervals(PreferencesUtils.isMetricUnits(), selectedInterval);
|
||||||
if (spinnerAdapter != null) {
|
if (spinnerAdapter != null) {
|
||||||
spinnerAdapter.notifyDataSetChanged();
|
spinnerAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
@@ -93,8 +91,6 @@ public class IntervalsFragment extends Fragment {
|
|||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
|
||||||
|
|
||||||
adapter = new IntervalStatisticsAdapter(getContext(), stackModeListView, metricUnits, isReportSpeed);
|
adapter = new IntervalStatisticsAdapter(getContext(), stackModeListView, metricUnits, isReportSpeed);
|
||||||
viewBinding.intervalList.setLayoutManager(new LinearLayoutManager(getContext()));
|
viewBinding.intervalList.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
// TODO handle empty view: before we did viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
|
// TODO handle empty view: before we did viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
|
||||||
@@ -136,13 +132,12 @@ public class IntervalsFragment extends Fragment {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
|
||||||
|
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getContext());
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getContext());
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
isReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
|
isReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel = new ViewModelProvider(getActivity()).get(IntervalStatisticsModel.class);
|
viewModel = new ViewModelProvider(getActivity()).get(IntervalStatisticsModel.class);
|
||||||
@@ -152,7 +147,9 @@ public class IntervalsFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
|
||||||
|
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
|
||||||
if (viewModel != null) {
|
if (viewModel != null) {
|
||||||
viewModel.onPause();
|
viewModel.onPause();
|
||||||
}
|
}
|
||||||
@@ -168,8 +165,6 @@ public class IntervalsFragment extends Fragment {
|
|||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
sharedPreferences = null;
|
|
||||||
|
|
||||||
adapter = null;
|
adapter = null;
|
||||||
viewModel = null;
|
viewModel = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,9 +254,9 @@ public class MarkerDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private void setLocation(boolean addShadow) {
|
private void setLocation(boolean addShadow) {
|
||||||
String value = "[" + getString(R.string.stats_latitude) + " "
|
String value = "[" + getString(R.string.stats_latitude) + " "
|
||||||
+ StringUtils.formatCoordinate(marker.getLatitude()) + ", "
|
+ StringUtils.formatCoordinate(getContext(), marker.getLatitude()) + ", "
|
||||||
+ getString(R.string.stats_longitude) + " "
|
+ getString(R.string.stats_longitude) + " "
|
||||||
+ StringUtils.formatCoordinate(marker.getLongitude()) + "]";
|
+ StringUtils.formatCoordinate(getContext(), marker.getLongitude()) + "]";
|
||||||
|
|
||||||
ListItemUtils.setTextView(getActivity(), viewBinding.markerDetailMarkerLocation, value, addShadow);
|
ListItemUtils.setTextView(getActivity(), viewBinding.markerDetailMarkerLocation, value, addShadow);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,21 +31,21 @@ import androidx.fragment.app.Fragment;
|
|||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
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.data.Track;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
|
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
|
||||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
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}.
|
* 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 SensorStatistics sensorStatistics;
|
||||||
private SensorsAdapter sensorsAdapter;
|
private StatisticsAdapter.WithRecordedLayout sensorsAdapter;
|
||||||
|
|
||||||
private Track.Id trackId;
|
private Track.Id trackId;
|
||||||
@Nullable // Lazily loaded.
|
@Nullable // Lazily loaded.
|
||||||
@@ -79,21 +79,20 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
|
|
||||||
private StatisticsRecordedBinding viewBinding;
|
private StatisticsRecordedBinding viewBinding;
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
private boolean preferenceMetricUnits;
|
private boolean preferenceMetricUnits;
|
||||||
private boolean preferenceReportSpeed;
|
private boolean preferenceReportSpeed;
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
boolean updateUInecessary = false;
|
boolean updateUInecessary = false;
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||||
updateUInecessary = true;
|
updateUInecessary = true;
|
||||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
preferenceMetricUnits = PreferencesUtils.isMetricUnits();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && track != null) {
|
if (PreferencesUtils.isKey(R.string.stats_rate_key, key) && track != null) {
|
||||||
updateUInecessary = true;
|
updateUInecessary = true;
|
||||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), track.getCategory());
|
preferenceReportSpeed = PreferencesUtils.isReportSpeed(track.getCategory());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key != null && updateUInecessary && isResumed()) {
|
if (key != null && updateUInecessary && isResumed()) {
|
||||||
@@ -112,9 +111,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
trackId = getArguments().getParcelable(TRACK_ID_KEY);
|
trackId = getArguments().getParcelable(TRACK_ID_KEY);
|
||||||
contentProviderUtils = new ContentProviderUtils(getContext());
|
contentProviderUtils = new ContentProviderUtils(getContext());
|
||||||
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
|
||||||
|
|
||||||
sensorsAdapter = new SensorsAdapter(getContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -132,24 +129,22 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
|
||||||
|
|
||||||
loadStatistics();
|
loadStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
|
||||||
|
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
viewBinding = null;
|
viewBinding = null;
|
||||||
|
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
sharedPreferences = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadStatistics() {
|
public void loadStatistics() {
|
||||||
@@ -168,7 +163,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
boolean prefsChanged = this.track == null || (!this.track.getCategory().equals(track.getCategory()));
|
boolean prefsChanged = this.track == null || (!this.track.getCategory().equals(track.getCategory()));
|
||||||
this.track = track;
|
this.track = track;
|
||||||
if (prefsChanged) {
|
if (prefsChanged) {
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTrackDescription(track);
|
loadTrackDescription(track);
|
||||||
@@ -238,22 +233,21 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
|
|
||||||
// Set altitude gain and loss
|
// 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 altitudeGain_m = trackStatistics.getTotalAltitudeGain();
|
||||||
Float altitudeLoss_m = trackStatistics.getTotalAltitudeLoss();
|
Float altitudeLoss_m = trackStatistics.getTotalAltitudeLoss();
|
||||||
|
|
||||||
Pair<String, String> parts;
|
Pair<String, String> parts;
|
||||||
|
|
||||||
parts = StringUtils.getAltitudeParts(getContext(), altitudeGain_m, preferenceMetricUnits);
|
parts = StringUtils.getAltitudeChangeParts(getContext(), altitudeGain_m, preferenceMetricUnits);
|
||||||
viewBinding.statsAltitudeGainValue.setText(parts.first);
|
viewBinding.statsAltitudeGainValue.setText(parts.first);
|
||||||
viewBinding.statsAltitudeGainUnit.setText(parts.second);
|
viewBinding.statsAltitudeGainUnit.setText(parts.second);
|
||||||
|
|
||||||
parts = StringUtils.getAltitudeParts(getContext(), altitudeLoss_m, preferenceMetricUnits);
|
parts = StringUtils.getAltitudeChangeParts(getContext(), altitudeLoss_m, preferenceMetricUnits);
|
||||||
viewBinding.statsAltitudeLossValue.setText(parts.first);
|
viewBinding.statsAltitudeLossValue.setText(parts.first);
|
||||||
viewBinding.statsAltitudeLossUnit.setText(parts.second);
|
viewBinding.statsAltitudeLossUnit.setText(parts.second);
|
||||||
|
|
||||||
|
boolean show = altitudeGain_m != null && altitudeLoss_m != null;
|
||||||
|
viewBinding.statsAltitudeGroup.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,18 +256,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SensorDataModel> sensorDataList = new ArrayList<>();
|
List<StatisticData> sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics);
|
||||||
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()));
|
|
||||||
}
|
|
||||||
if (sensorDataList.size() > 0) {
|
if (sensorDataList.size() > 0) {
|
||||||
sensorsAdapter.swapData(sensorDataList);
|
sensorsAdapter.swapData(sensorDataList);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.fragments;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -11,28 +10,25 @@ import android.view.ViewGroup;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
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.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
||||||
import de.dennisguse.opentracks.adapters.SensorsAdapter;
|
import de.dennisguse.opentracks.content.data.Layout;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
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.databinding.StatisticsRecordingBinding;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
|
||||||
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment to display track statistics to the user for a currently recording {@link Track}.
|
* A fragment to display track statistics to the user for a currently recording {@link Track}.
|
||||||
@@ -51,37 +47,31 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||||
private TrackRecordingService.RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
|
private TrackRecordingService.RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
|
||||||
private TrackPoint latestTrackPoint;
|
private TrackPoint latestTrackPoint;
|
||||||
|
private Layout layout;
|
||||||
|
|
||||||
private StatisticsRecordingBinding viewBinding;
|
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 preferenceMetricUnits;
|
||||||
private boolean preferenceReportSpeed;
|
|
||||||
private boolean preferenceShowAltitude;
|
|
||||||
private boolean preferenceShowCoordinate;
|
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
boolean updateUInecessary = false;
|
boolean updateUInecessary = false;
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||||
updateUInecessary = true;
|
updateUInecessary = true;
|
||||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
|
preferenceMetricUnits = PreferencesUtils.isMetricUnits();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && recordingData != null) {
|
if (PreferencesUtils.isKey(R.string.stats_custom_layout_fields_key, key)) {
|
||||||
updateUInecessary = true;
|
updateUInecessary = true;
|
||||||
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), recordingData.getTrackCategory());
|
layout = PreferencesUtils.getCustomLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_grade_altitude_key, key)) {
|
if (PreferencesUtils.isKey(R.string.stats_custom_layout_columns_key, key)) {
|
||||||
updateUInecessary = true;
|
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns());
|
||||||
preferenceShowAltitude = PreferencesUtils.isShowStatsAltitude(sharedPreferences, getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_coordinate_key, key)) {
|
|
||||||
updateUInecessary = true;
|
|
||||||
preferenceShowCoordinate = PreferencesUtils.isStatsShowCoordinate(sharedPreferences, getContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key != null && updateUInecessary && isResumed()) {
|
if (key != null && updateUInecessary && isResumed()) {
|
||||||
@@ -106,20 +96,30 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
|
|
||||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||||
|
|
||||||
sensorsAdapter = new SensorsAdapter(getContext());
|
statisticsAdapter = new StatisticsAdapter(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
||||||
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(recordingData.getTrackCategory()));
|
|
||||||
|
|
||||||
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
RecyclerView recyclerView = viewBinding.statsRecyclerView;
|
||||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
|
||||||
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
final int numColumns = PreferencesUtils.getLayoutColumns();
|
||||||
|
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();
|
return viewBinding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -128,8 +128,11 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.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());
|
trackRecordingServiceConnection.startConnection(getContext());
|
||||||
}
|
}
|
||||||
@@ -137,8 +140,7 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -157,168 +159,27 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
trackRecordingServiceConnection = null;
|
trackRecordingServiceConnection = null;
|
||||||
sharedPreferences = null;
|
viewModel = null;
|
||||||
|
|
||||||
|
if (statisticsLiveData != null) {
|
||||||
|
statisticsLiveData.removeObservers(getActivity());
|
||||||
|
}
|
||||||
|
statisticsLiveData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUI() {
|
private void updateUI() {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
updateStats();
|
viewModel.update(recordingData, layout, preferenceMetricUnits);
|
||||||
setLocationValues();
|
|
||||||
updateSensorDataUI();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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().getSpeed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
private void onRecordingDataChanged(TrackRecordingService.RecordingData recordingData) {
|
||||||
String oldCategory = this.recordingData.getTrackCategory();
|
String oldCategory = this.recordingData.getTrackCategory();
|
||||||
String newCategory = recordingData.getTrackCategory();
|
String newCategory = recordingData.getTrackCategory();
|
||||||
this.recordingData = recordingData;
|
this.recordingData = recordingData;
|
||||||
|
|
||||||
if (!oldCategory.equals(newCategory)) {
|
if (!oldCategory.equals(newCategory)) {
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
|
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
|
||||||
}
|
}
|
||||||
|
|
||||||
latestTrackPoint = recordingData.getLatestTrackPoint();
|
latestTrackPoint = recordingData.getLatestTrackPoint();
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import de.dennisguse.opentracks.content.data.Track;
|
|||||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.databinding.ExportActivityBinding;
|
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.ErrorListDialog;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.util.ExportUtils;
|
import de.dennisguse.opentracks.util.ExportUtils;
|
||||||
@@ -134,6 +135,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewBinding = ExportActivityBinding.inflate(getLayoutInflater());
|
viewBinding = ExportActivityBinding.inflate(getLayoutInflater());
|
||||||
|
ToolbarBinding mergedBinding = ToolbarBinding.bind(viewBinding.getRoot());
|
||||||
setContentView(viewBinding.getRoot());
|
setContentView(viewBinding.getRoot());
|
||||||
|
|
||||||
directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
||||||
@@ -144,7 +146,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
|||||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
|
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
|
||||||
String directoryDisplayName = FileUtils.getPath(documentFile);
|
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);
|
resultReceiver = new ExportServiceResultReceiver(new Handler(), this);
|
||||||
|
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
|
|
||||||
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
|
||||||
|
|
||||||
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCyclingCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) {
|
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) {
|
||||||
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
||||||
|
|
||||||
if (trackPoint.hasSpeed()) {
|
if (trackPoint.hasSpeed()) {
|
||||||
@@ -296,8 +296,8 @@ public class GPXTrackExporter implements TrackExporter {
|
|||||||
printWriter.println("<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>");
|
printWriter.println("<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackPoint.hasCyclingCadence()) {
|
if (trackPoint.hasCadence()) {
|
||||||
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCyclingCadence_rpm()) + "</gpxtpx:cad>");
|
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + "</gpxtpx:cad>");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackPoint.hasPower()) {
|
if (trackPoint.hasPower()) {
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.text.NumberFormat;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
@@ -67,6 +69,13 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
private static final String MARKER_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
private static final String MARKER_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
||||||
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
|
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
|
||||||
|
|
||||||
|
private static final NumberFormat SENSOR_DATA_FORMAT = NumberFormat.getInstance(Locale.US);
|
||||||
|
|
||||||
|
static {
|
||||||
|
SENSOR_DATA_FORMAT.setMaximumFractionDigits(1);
|
||||||
|
SENSOR_DATA_FORMAT.setGroupingUsed(false);
|
||||||
|
}
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final boolean exportPhotos;
|
private final boolean exportPhotos;
|
||||||
private final ContentProviderUtils contentProviderUtils;
|
private final ContentProviderUtils contentProviderUtils;
|
||||||
@@ -373,7 +382,7 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
|
|
||||||
distanceList.add(trackPoint.hasSensorDistance() ? (float) trackPoint.getSensorDistance().toM() : null);
|
distanceList.add(trackPoint.hasSensorDistance() ? (float) trackPoint.getSensorDistance().toM() : null);
|
||||||
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
|
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
|
||||||
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
|
cadenceList.add(trackPoint.hasCadence() ? trackPoint.getCadence_rpm() : null);
|
||||||
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
|
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
|
||||||
|
|
||||||
altitudeGainList.add(trackPoint.hasAltitudeGain() ? trackPoint.getAltitudeGain() : null);
|
altitudeGainList.add(trackPoint.hasAltitudeGain() ? trackPoint.getAltitudeGain() : null);
|
||||||
@@ -394,7 +403,7 @@ public class KMLTrackExporter implements TrackExporter {
|
|||||||
if (value == null) {
|
if (value == null) {
|
||||||
printWriter.println("<gx:value />");
|
printWriter.println("<gx:value />");
|
||||||
} else {
|
} else {
|
||||||
printWriter.println("<gx:value>" + list.get(i) + "</gx:value>");
|
printWriter.println("<gx:value>" + SENSOR_DATA_FORMAT.format(value) + "</gx:value>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
printWriter.println("</gx:SimpleArrayData>");
|
printWriter.println("</gx:SimpleArrayData>");
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.io.OutputStream;
|
|||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.databinding.TrackSharePictureBinding;
|
import de.dennisguse.opentracks.databinding.TrackSharePictureBinding;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,12 +61,12 @@ public class SharePictureTrackExporter implements TrackExporter {
|
|||||||
TrackSharePictureBinding viewBinding = TrackSharePictureBinding.inflate(LayoutInflater.from(context));
|
TrackSharePictureBinding viewBinding = TrackSharePictureBinding.inflate(LayoutInflater.from(context));
|
||||||
viewBinding.sharePictureCategory.setText(track.getCategory());
|
viewBinding.sharePictureCategory.setText(track.getCategory());
|
||||||
|
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(PreferencesUtils.getSharedPreferences(context), context);
|
boolean metricUnits = PreferencesUtils.isMetricUnits();
|
||||||
TrackStatistics trackStatistics = track.getTrackStatistics();
|
TrackStatistics trackStatistics = track.getTrackStatistics();
|
||||||
|
|
||||||
viewBinding.sharePictureTotalTime.setText(StringUtils.formatElapsedTimeWithHour(track.getTrackStatistics().getTotalTime()));
|
viewBinding.sharePictureTotalTime.setText(StringUtils.formatElapsedTimeWithHour(track.getTrackStatistics().getTotalTime()));
|
||||||
viewBinding.sharePictureTotalDistance.setText(StringUtils.formatDistance(context, track.getTrackStatistics().getTotalDistance(), metricUnits));
|
viewBinding.sharePictureTotalDistance.setText(StringUtils.formatDistance(context, track.getTrackStatistics().getTotalDistance(), metricUnits));
|
||||||
viewBinding.sharePictureGain.setText(StringUtils.formatAltitude(context, trackStatistics.getTotalAltitudeGain(), metricUnits));
|
viewBinding.sharePictureGain.setText(StringUtils.formatAltitudeChange(context, trackStatistics.getTotalAltitudeGain(), metricUnits));
|
||||||
|
|
||||||
// prepare rendering
|
// prepare rendering
|
||||||
View view = viewBinding.getRoot();
|
View view = viewBinding.getRoot();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.io.file.importer;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
@@ -13,20 +12,16 @@ import androidx.documentfile.provider.DocumentFile;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
|
import de.dennisguse.opentracks.io.file.exporter.ExportActivity;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
|
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
|
||||||
|
|
||||||
protected SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
|
||||||
|
|
||||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
|
DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
|
||||||
if (directoryUri == null) {
|
if (directoryUri == null) {
|
||||||
@@ -43,12 +38,6 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
|||||||
onActivityResultCustom(requestCode, resultCode, resultData);
|
onActivityResultCustom(requestCode, resultCode, resultData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
sharedPreferences = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) {
|
protected void onActivityResultCustom(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||||
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
@@ -91,14 +80,14 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
|||||||
super.configureDirectoryChooserIntent(intent);
|
super.configureDirectoryChooserIntent(intent);
|
||||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||||
|
|
||||||
return PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this);
|
return PreferencesUtils.getDefaultExportDirectoryUri(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Intent createNextActivityIntent(Uri directoryUri) {
|
protected Intent createNextActivityIntent(Uri directoryUri) {
|
||||||
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
|
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
|
||||||
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
|
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
|
||||||
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat(sharedPreferences, this));
|
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat());
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,11 +101,11 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
|||||||
case RESULT_OK:
|
case RESULT_OK:
|
||||||
Uri directoryUri = resultData.getData();
|
Uri directoryUri = resultData.getData();
|
||||||
|
|
||||||
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, directoryUri);
|
PreferencesUtils.setDefaultExportDirectoryUri(directoryUri);
|
||||||
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri);
|
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri);
|
||||||
break;
|
break;
|
||||||
case RESULT_CANCELED:
|
case RESULT_CANCELED:
|
||||||
PreferencesUtils.setDefaultExportDirectoryUri(sharedPreferences, this, null);
|
PreferencesUtils.setDefaultExportDirectoryUri(null);
|
||||||
//TODO Remove stored permission contentResolver.releasePersistableUriPermission
|
//TODO Remove stored permission contentResolver.releasePersistableUriPermission
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -129,8 +118,8 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
|||||||
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
|
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
|
||||||
super.configureDirectoryChooserIntent(intent);
|
super.configureDirectoryChooserIntent(intent);
|
||||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
if (PreferencesUtils.isDefaultExportDirectoryUri(sharedPreferences, this)) {
|
if (PreferencesUtils.isDefaultExportDirectoryUri(this)) {
|
||||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(sharedPreferences, this).getUri());
|
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(this).getUri());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
private static final String ATTRIBUTE_LON = "lon";
|
private static final String ATTRIBUTE_LON = "lon";
|
||||||
|
|
||||||
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
|
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
|
||||||
|
/**
|
||||||
|
* Often speed is exported without the proper namespace.
|
||||||
|
*/
|
||||||
|
private static final String TAG_EXTENSION_SPEED_COMPAT = "speed";
|
||||||
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
||||||
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
||||||
private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts";
|
private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts";
|
||||||
@@ -192,6 +196,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TAG_EXTENSION_SPEED:
|
case TAG_EXTENSION_SPEED:
|
||||||
|
case TAG_EXTENSION_SPEED_COMPAT:
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
speed = content.trim();
|
speed = content.trim();
|
||||||
}
|
}
|
||||||
@@ -295,7 +300,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
|
|
||||||
if (cadence != null) {
|
if (cadence != null) {
|
||||||
try {
|
try {
|
||||||
trackPoint.setCyclingCadence_rpm(Float.parseFloat(cadence));
|
trackPoint.setCadence_rpm(Float.parseFloat(cadence));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse cadence: %s", cadence)), e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.io.file.importer;
|
|||||||
import android.app.job.JobService;
|
import android.app.job.JobService;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.ResultReceiver;
|
import android.os.ResultReceiver;
|
||||||
@@ -22,7 +21,7 @@ import de.dennisguse.opentracks.content.data.Track;
|
|||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
|
||||||
public class ImportService extends JobIntentService {
|
public class ImportService extends JobIntentService {
|
||||||
|
|
||||||
@@ -54,10 +53,9 @@ public class ImportService extends JobIntentService {
|
|||||||
|
|
||||||
String fileExtension = FileUtils.getExtension(file);
|
String fileExtension = FileUtils.getExtension(file);
|
||||||
try {
|
try {
|
||||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
|
||||||
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, this);
|
Distance recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval();
|
||||||
Distance recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, this);
|
boolean preventReimport = PreferencesUtils.getPreventReimportTracks();
|
||||||
boolean preventReimport = PreferencesUtils.getPreventReimportTracks(sharedPreferences, this);
|
|
||||||
|
|
||||||
TrackImporter trackImporter = new TrackImporter(this, new ContentProviderUtils(this), recordingDistanceInterval, maxRecordingDistance, preventReimport);
|
TrackImporter trackImporter = new TrackImporter(this, new ContentProviderUtils(this), recordingDistanceInterval, maxRecordingDistance, preventReimport);
|
||||||
|
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
|||||||
trackPoint.setHeartRate_bpm(sensorHeartRateList.get(i));
|
trackPoint.setHeartRate_bpm(sensorHeartRateList.get(i));
|
||||||
}
|
}
|
||||||
if (i < sensorCadenceList.size()) {
|
if (i < sensorCadenceList.size()) {
|
||||||
trackPoint.setCyclingCadence_rpm(sensorCadenceList.get(i));
|
trackPoint.setCadence_rpm(sensorCadenceList.get(i));
|
||||||
}
|
}
|
||||||
if (i < sensorPowerList.size()) {
|
if (i < sensorPowerList.size()) {
|
||||||
trackPoint.setPower(sensorPowerList.get(i));
|
trackPoint.setPower(sensorPowerList.get(i));
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ public class TrackImporter {
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
adjustTrackPoints();
|
||||||
|
|
||||||
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
|
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
|
||||||
updater.addTrackPoints(trackPoints, recordingDistanceInterval);
|
updater.addTrackPoints(trackPoints, recordingDistanceInterval);
|
||||||
track.setTrackStatistics(updater.getTrackStatistics());
|
track.setTrackStatistics(updater.getTrackStatistics());
|
||||||
@@ -151,7 +153,6 @@ public class TrackImporter {
|
|||||||
Track.Id trackId = contentProviderUtils.insertTrack(track);
|
Track.Id trackId = contentProviderUtils.insertTrack(track);
|
||||||
|
|
||||||
// Store TrackPoints
|
// Store TrackPoints
|
||||||
adjustTrackPoints();
|
|
||||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, trackId);
|
contentProviderUtils.bulkInsertTrackPoint(trackPoints, trackId);
|
||||||
|
|
||||||
// Store Markers
|
// Store Markers
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.services;
|
|||||||
|
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -20,7 +19,7 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
|||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackNameUtils;
|
import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||||
|
|
||||||
@@ -34,12 +33,11 @@ class TrackRecordingManager {
|
|||||||
private Distance recordingDistanceInterval;
|
private Distance recordingDistanceInterval;
|
||||||
private Distance maxRecordingDistance;
|
private Distance maxRecordingDistance;
|
||||||
|
|
||||||
private Track track;
|
private Track.Id trackId;
|
||||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||||
|
|
||||||
private TrackPoint lastTrackPoint;
|
private boolean currentSegmentHasTrackPoint;
|
||||||
private TrackPoint lastValidTrackPoint;
|
private TrackPoint lastStoredTrackPoint;
|
||||||
private boolean isIdle;
|
|
||||||
|
|
||||||
TrackRecordingManager(Context context) {
|
TrackRecordingManager(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@@ -47,70 +45,74 @@ class TrackRecordingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Track.Id start(TrackPoint segmentStartTrackPoint) {
|
Track.Id start(TrackPoint segmentStartTrackPoint) {
|
||||||
// Insert a track
|
// Create new track
|
||||||
track = new Track();
|
Track track = new Track();
|
||||||
track.setId(contentProviderUtils.insertTrack(track));
|
trackId = contentProviderUtils.insertTrack(track);
|
||||||
|
track.setId(trackId);
|
||||||
|
|
||||||
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||||
|
|
||||||
insertTrackPoint(track, segmentStartTrackPoint);
|
insertTrackPoint(trackId, segmentStartTrackPoint);
|
||||||
|
|
||||||
//TODO Pass TrackPoint
|
//TODO Pass TrackPoint
|
||||||
track.setName(TrackNameUtils.getTrackName(context, track.getId(), segmentStartTrackPoint.getTime()));
|
track.setName(TrackNameUtils.getTrackName(context, trackId, segmentStartTrackPoint.getTime()));
|
||||||
|
|
||||||
String category = PreferencesUtils.getDefaultActivity(PreferencesUtils.getSharedPreferences(context), context); //TODO Re-use sharedpreferences
|
String category = PreferencesUtils.getDefaultActivity();
|
||||||
track.setCategory(category);
|
track.setCategory(category);
|
||||||
track.setIcon(TrackIconUtils.getIconValue(context, category));
|
track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
||||||
contentProviderUtils.updateTrack(track); //TODO Could we do this in the insert?
|
contentProviderUtils.updateTrack(track);
|
||||||
|
|
||||||
return track.getId();
|
currentSegmentHasTrackPoint = false;
|
||||||
|
|
||||||
|
return trackId;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Handle non-existing trackId? Start a new track or exception?
|
//TODO Handle non-existing trackId? Start a new track or exception?
|
||||||
void resume(@NonNull Track.Id trackId, @NonNull TrackPoint segmentStartTrackPoint) {
|
void resume(@NonNull Track.Id resumeTrackId, @NonNull TrackPoint segmentStartTrackPoint) {
|
||||||
track = contentProviderUtils.getTrack(trackId);
|
trackId = resumeTrackId;
|
||||||
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists.");
|
Log.e(TAG, "Ignore resumeTrack. Track " + trackId.getId() + " does not exists.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
||||||
insertTrackPoint(track, segmentStartTrackPoint);
|
insertTrackPoint(trackId, segmentStartTrackPoint);
|
||||||
|
currentSegmentHasTrackPoint = false;
|
||||||
|
lastStoredTrackPoint = null; }
|
||||||
|
|
||||||
|
void pause(TrackPointCreator trackPointCreator) {
|
||||||
|
insertTrackPoint(trackId, trackPointCreator.createSegmentEnd());
|
||||||
|
currentSegmentHasTrackPoint = false;
|
||||||
|
lastStoredTrackPoint = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pause(TrackPointCreator handlerServer) {
|
void end(TrackPointCreator trackPointCreator) {
|
||||||
if (lastTrackPoint != null) {
|
TrackPoint segmentEnd = trackPointCreator.createSegmentEnd();
|
||||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
insertTrackPoint(trackId, segmentEnd);
|
||||||
}
|
|
||||||
insertTrackPoint(track, handlerServer.createSegmentEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
void end(TrackPointCreator handlerServer) {
|
trackId = null;
|
||||||
if (lastTrackPoint != null) {
|
|
||||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackPoint segmentEnd = handlerServer.createSegmentEnd();
|
|
||||||
insertTrackPoint(track, segmentEnd);
|
|
||||||
|
|
||||||
track = null;
|
|
||||||
trackStatisticsUpdater = null;
|
trackStatisticsUpdater = null;
|
||||||
lastTrackPoint = null;
|
lastStoredTrackPoint = null;
|
||||||
lastValidTrackPoint = null;
|
currentSegmentHasTrackPoint = false;
|
||||||
isIdle = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair<Track, Pair<TrackPoint, SensorDataSet>> get(TrackPointCreator handlerServer) {
|
Pair<Track, Pair<TrackPoint, SensorDataSet>> get(TrackPointCreator trackPointCreator) {
|
||||||
if (handlerServer == null) {
|
if (trackPointCreator == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
TrackStatisticsUpdater tmpTrackStatisticsUpdater = getTrackStatisticsUpdater();
|
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater);
|
||||||
Pair<TrackPoint, SensorDataSet> current = handlerServer.createCurrentTrackPoint(lastTrackPoint);
|
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastStoredTrackPoint);
|
||||||
|
|
||||||
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
|
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
|
||||||
|
|
||||||
Track track = getTrack(); //Get copy
|
Track track = contentProviderUtils.getTrack(trackId); //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());
|
track.setTrackStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
|
||||||
|
|
||||||
return new Pair<>(track, current);
|
return new Pair<>(track, current);
|
||||||
@@ -118,15 +120,14 @@ class TrackRecordingManager {
|
|||||||
|
|
||||||
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
|
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(track.getId());
|
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(trackId);
|
||||||
if (nextMarkerNumber == null) {
|
if (nextMarkerNumber == null) {
|
||||||
nextMarkerNumber = 1;
|
nextMarkerNumber = 1;
|
||||||
}
|
}
|
||||||
name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1);
|
name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
if (lastStoredTrackPoint == null) {
|
||||||
if (trackPoint == null) {
|
|
||||||
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
|
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -136,138 +137,51 @@ class TrackRecordingManager {
|
|||||||
String icon = context.getString(R.string.marker_icon_url);
|
String icon = context.getString(R.string.marker_icon_url);
|
||||||
photoUrl = photoUrl != null ? photoUrl : "";
|
photoUrl = photoUrl != null ? photoUrl : "";
|
||||||
|
|
||||||
|
|
||||||
// Insert marker
|
// Insert marker
|
||||||
Marker marker = new Marker(name, description, category, icon, track.getId(), getTrackStatistics(), trackPoint, photoUrl);
|
Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), lastStoredTrackPoint, photoUrl);
|
||||||
Uri uri = contentProviderUtils.insertMarker(marker);
|
Uri uri = contentProviderUtils.insertMarker(marker);
|
||||||
return new Marker.Id(ContentUris.parseId(uri));
|
return new Marker.Id(ContentUris.parseId(uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onNewTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
|
boolean onNewTrackPoint(TrackPoint trackPoint) {
|
||||||
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
|
|
||||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
|
||||||
|
|
||||||
//Storing trackPoint
|
//Storing trackPoint
|
||||||
|
|
||||||
// Always insert the first segment location
|
// Always insert the first segment location
|
||||||
if (!currentSegmentHasTrackPoint()) {
|
if (!currentSegmentHasTrackPoint) {
|
||||||
insertTrackPoint(track, trackPoint);
|
insertTrackPoint(trackId, trackPoint);
|
||||||
lastTrackPoint = trackPoint;
|
currentSegmentHasTrackPoint = true;
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
|
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastStoredTrackPoint);
|
||||||
|
if (distanceToLastTrackLocation != null) {
|
||||||
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
|
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
|
||||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
|
||||||
|
|
||||||
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||||
insertTrackPoint(track, trackPoint);
|
insertTrackPoint(trackId, trackPoint);
|
||||||
|
return true;
|
||||||
isIdle = false;
|
|
||||||
lastTrackPoint = trackPoint;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) {
|
if (trackPoint.hasSensorData() || (distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving())) {
|
||||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
insertTrackPoint(trackId, trackPoint);
|
||||||
|
return true;
|
||||||
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");
|
Log.d(TAG, "Not recording TrackPoint, idle");
|
||||||
lastTrackPoint = trackPoint;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
TrackStatistics getTrackStatistics() {
|
||||||
return trackStatisticsUpdater.getTrackStatistics();
|
return trackStatisticsUpdater.getTrackStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void insertTrackPoint(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
||||||
* 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 {
|
try {
|
||||||
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
|
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
|
||||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||||
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
|
|
||||||
|
|
||||||
contentProviderUtils.updateTrack(track);
|
contentProviderUtils.updateTrackStatistics(trackId, trackStatisticsUpdater.getTrackStatistics());
|
||||||
|
lastStoredTrackPoint = trackPoint;
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException e) {
|
||||||
/*
|
/*
|
||||||
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
|
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
|
||||||
@@ -277,12 +191,12 @@ class TrackRecordingManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSharedPreferenceChanged(@NonNull SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(String key) {
|
||||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
if (PreferencesUtils.isKey(R.string.recording_distance_interval_key, key)) {
|
||||||
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval();
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(context, R.string.max_recording_distance_key, key)) {
|
if (PreferencesUtils.isKey(R.string.max_recording_distance_key, key)) {
|
||||||
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context);
|
maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,14 +49,12 @@ import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
|
|||||||
import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager;
|
import de.dennisguse.opentracks.services.handlers.EGM2008CorrectionManager;
|
||||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTask;
|
import de.dennisguse.opentracks.services.tasks.VoiceAnnouncementManager;
|
||||||
import de.dennisguse.opentracks.services.tasks.PeriodicTask;
|
|
||||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.util.ExportUtils;
|
import de.dennisguse.opentracks.util.ExportUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.SystemUtils;
|
import de.dennisguse.opentracks.util.SystemUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,12 +69,12 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
private static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
private static final Duration RECORDING_DATA_UPDATE_INTERVAL = Duration.ofSeconds(1);
|
||||||
|
|
||||||
public static final RecordingStatus STATUS_DEFAULT = new RecordingStatus(null, false);
|
public static final RecordingStatus STATUS_DEFAULT = RecordingStatus.notRecording();
|
||||||
public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null);
|
public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null);
|
||||||
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
|
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
|
||||||
|
|
||||||
// The following variables are set in onCreate:
|
// The following variables are setFrequency in onCreate:
|
||||||
private PeriodicTaskExecutor voiceExecutor;
|
private VoiceAnnouncementManager voiceAnnouncementManager;
|
||||||
private TrackRecordingServiceNotificationManager notificationManager;
|
private TrackRecordingServiceNotificationManager notificationManager;
|
||||||
|
|
||||||
private TrackRecordingManager trackRecordingManager;
|
private TrackRecordingManager trackRecordingManager;
|
||||||
@@ -98,33 +96,31 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
Context context = TrackRecordingService.this;
|
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||||
if (PreferencesUtils.isKey(context, R.string.stats_units_key, key)) {
|
boolean metricUnits = PreferencesUtils.isMetricUnits();
|
||||||
boolean metricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, context);
|
|
||||||
voiceExecutor.setMetricUnits(metricUnits);
|
|
||||||
notificationManager.setMetricUnits(metricUnits);
|
notificationManager.setMetricUnits(metricUnits);
|
||||||
}
|
}
|
||||||
if (PreferencesUtils.isKey(context, R.string.voice_frequency_key, key)) {
|
if (PreferencesUtils.isKey(R.string.voice_announcement_frequency_key, key)) {
|
||||||
voiceExecutor.setTaskFrequency(PreferencesUtils.getVoiceFrequency(sharedPreferences, context));
|
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementFrequency());
|
||||||
|
}
|
||||||
|
if (PreferencesUtils.isKey(new int[]{R.string.voice_announcement_distance_key, R.string.stats_units_key}, key)) {
|
||||||
|
voiceAnnouncementManager.setFrequency(PreferencesUtils.getVoiceAnnouncementDistance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trackPointCreator.onSharedPreferenceChanged(key);
|
||||||
handlerServer.onSharedPreferenceChanged(sharedPreferences, key);
|
trackRecordingManager.onSharedPreferenceChanged(key);
|
||||||
trackRecordingManager.onSharedPreferenceChanged(sharedPreferences, key);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The following variables are set when recording:
|
// The following variables are setFrequency when recording:
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
|
|
||||||
private final Binder binder = new Binder();
|
private final Binder binder = new Binder();
|
||||||
|
|
||||||
private TrackPointCreator handlerServer; //TODO Move to TrackRecordingManager?
|
private TrackPointCreator trackPointCreator; //TODO Move to TrackRecordingManager?
|
||||||
|
|
||||||
private RecordingStatus recordingStatus;
|
private RecordingStatus recordingStatus;
|
||||||
private MutableLiveData<RecordingStatus> recordingStatusObservable;
|
private MutableLiveData<RecordingStatus> recordingStatusObservable;
|
||||||
@@ -143,15 +139,13 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
recordingDataObservable = new MutableLiveData<>(NOT_RECORDING);
|
recordingDataObservable = new MutableLiveData<>(NOT_RECORDING);
|
||||||
|
|
||||||
trackRecordingManager = new TrackRecordingManager(this);
|
trackRecordingManager = new TrackRecordingManager(this);
|
||||||
handlerServer = new TrackPointCreator(this);
|
trackPointCreator = new TrackPointCreator(this);
|
||||||
|
|
||||||
voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTask.Factory());
|
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
|
||||||
|
|
||||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||||
|
|
||||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
|
||||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -168,19 +162,18 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
handler = null;
|
handler = null;
|
||||||
|
|
||||||
handlerServer.stop();
|
trackPointCreator.stop();
|
||||||
handlerServer = null;
|
trackPointCreator = null;
|
||||||
|
|
||||||
// Reverse order from onCreate
|
// Reverse order from onCreate
|
||||||
showNotification(false); //TODO Why?
|
showNotification(false); //TODO Why?
|
||||||
|
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
sharedPreferences = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
voiceExecutor.shutdown();
|
voiceAnnouncementManager.shutdown();
|
||||||
} finally {
|
} finally {
|
||||||
voiceExecutor = null;
|
voiceAnnouncementManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should be the next to last operation
|
// This should be the next to last operation
|
||||||
@@ -209,10 +202,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
return trackRecordingManager.insertMarker(name, category, description, photoUrl);
|
return trackRecordingManager.insertMarker(name, category, description, photoUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run(@NonNull PeriodicTask periodicTask) {
|
|
||||||
periodicTask.run(recordingStatus.getTrackId(), trackRecordingManager.getTrackStatistics());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a new track.
|
* Starts a new track.
|
||||||
*
|
*
|
||||||
@@ -225,8 +214,8 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set recording status
|
// Set recording status
|
||||||
Track.Id trackId = trackRecordingManager.start(handlerServer.createSegmentStartManual());
|
Track.Id trackId = trackRecordingManager.start(trackPointCreator.createSegmentStartManual());
|
||||||
updateRecordingStatus(new RecordingStatus(trackId, false));
|
updateRecordingStatus(RecordingStatus.record(trackId));
|
||||||
|
|
||||||
startRecording();
|
startRecording();
|
||||||
return trackId;
|
return trackId;
|
||||||
@@ -239,11 +228,11 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
* @param trackId the id of the track to be resumed.
|
* @param trackId the id of the track to be resumed.
|
||||||
*/
|
*/
|
||||||
public void resumeTrack(Track.Id trackId) {
|
public void resumeTrack(Track.Id trackId) {
|
||||||
handlerServer.resetSensorData();
|
trackPointCreator.resetSensorData();
|
||||||
trackRecordingManager.resume(trackId, handlerServer.createSegmentStartManual());
|
trackRecordingManager.resume(trackId, trackPointCreator.createSegmentStartManual());
|
||||||
|
|
||||||
// Set recording status
|
// Set recording status
|
||||||
updateRecordingStatus(new RecordingStatus(trackId, false));
|
updateRecordingStatus(RecordingStatus.record(trackId));
|
||||||
|
|
||||||
startRecording();
|
startRecording();
|
||||||
}
|
}
|
||||||
@@ -267,8 +256,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
startGps();
|
startGps();
|
||||||
|
|
||||||
// Restore periodic tasks
|
voiceAnnouncementManager.restore(trackRecordingManager.getTrackStatistics());
|
||||||
voiceExecutor.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tryStartGps() {
|
public void tryStartGps() {
|
||||||
@@ -279,7 +267,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
private void startGps() {
|
private void startGps() {
|
||||||
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
|
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
|
||||||
handlerServer.start(this);
|
trackPointCreator.start(this);
|
||||||
showNotification(true);
|
showNotification(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +286,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
updateRecordingStatus(STATUS_DEFAULT);
|
updateRecordingStatus(STATUS_DEFAULT);
|
||||||
|
|
||||||
if (!wasPause) {
|
if (!wasPause) {
|
||||||
trackRecordingManager.end(handlerServer);
|
trackRecordingManager.end(trackPointCreator);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExportUtils.postWorkoutExport(this, trackId, new ExportServiceResultReceiver(new Handler(), this));
|
ExportUtils.postWorkoutExport(this, trackId, new ExportServiceResultReceiver(new Handler(), this));
|
||||||
@@ -314,9 +302,9 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set recording status
|
// Set recording status
|
||||||
updateRecordingStatus(new RecordingStatus(recordingStatus.getTrackId(), true));
|
updateRecordingStatus(recordingStatus.pause());
|
||||||
|
|
||||||
trackRecordingManager.pause(handlerServer);
|
trackRecordingManager.pause(trackPointCreator);
|
||||||
|
|
||||||
endRecording(false);
|
endRecording(false);
|
||||||
|
|
||||||
@@ -337,10 +325,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown periodic tasks
|
// Shutdown periodic tasks
|
||||||
voiceExecutor.shutdown();
|
voiceAnnouncementManager.shutdown();
|
||||||
|
|
||||||
// Update instance variables
|
// Update instance variables
|
||||||
handlerServer.stop();
|
trackPointCreator.stop();
|
||||||
|
|
||||||
stopGps(trackStopped);
|
stopGps(trackStopped);
|
||||||
}
|
}
|
||||||
@@ -357,7 +345,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
void stopGps(boolean shutdown) {
|
void stopGps(boolean shutdown) {
|
||||||
if (!isRecording()) return;
|
if (!isRecording()) return;
|
||||||
|
|
||||||
handlerServer.stop();
|
trackPointCreator.stop();
|
||||||
showNotification(false);
|
showNotification(false);
|
||||||
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
wakeLock = SystemUtils.releaseWakeLock(wakeLock);
|
||||||
if (shutdown) {
|
if (shutdown) {
|
||||||
@@ -366,15 +354,14 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
|
public boolean newTrackPoint(TrackPoint trackPoint, Distance thresholdHorizontalAccuracy) {
|
||||||
if (!isRecording() || isPaused()) {
|
if (!isRecording() || isPaused()) {
|
||||||
Log.w(TAG, "Ignore newTrackPoint. Not recording or paused.");
|
Log.w(TAG, "Ignore newTrackPoint. Not recording or paused.");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackRecordingManager.onNewTrackPoint(trackPoint, thresholdHorizontalAccuracy);
|
|
||||||
notificationManager.updateTrackPoint(this, trackRecordingManager.getTrackStatistics(), trackPoint, thresholdHorizontalAccuracy);
|
notificationManager.updateTrackPoint(this, trackRecordingManager.getTrackStatistics(), trackPoint, thresholdHorizontalAccuracy);
|
||||||
voiceExecutor.update(recordingStatus.getTrackId(), trackRecordingManager.getTrackStatistics());
|
return trackRecordingManager.onNewTrackPoint(trackPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -389,12 +376,16 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
private void showNotification(boolean isGpsStarted) {
|
private void showNotification(boolean isGpsStarted) {
|
||||||
if (isRecording()) {
|
if (isRecording()) {
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class);
|
||||||
.putExtra(TrackRecordingActivity.EXTRA_TRACK_ID, recordingStatus.getTrackId());
|
|
||||||
|
int pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||||
|
pendingIntentFlags |= PendingIntent.FLAG_IMMUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||||
.addParentStack(TrackRecordingActivity.class)
|
.addNextIntentWithParentStack(intent)
|
||||||
.addNextIntent(intent)
|
.getPendingIntent(0, pendingIntentFlags);
|
||||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
|
|
||||||
notificationManager.updatePendingIntent(pendingIntent);
|
notificationManager.updatePendingIntent(pendingIntent);
|
||||||
notificationManager.updateContent(getString(R.string.gps_starting));
|
notificationManager.updateContent(getString(R.string.gps_starting));
|
||||||
@@ -402,9 +393,15 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
if (!isRecording() && isGpsStarted) {
|
if (!isRecording() && isGpsStarted) {
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
|
Intent intent = IntentUtils.newIntent(this, TrackListActivity.class);
|
||||||
|
|
||||||
|
int pendingIntentFlags = 0;
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||||
|
pendingIntentFlags = PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
}
|
||||||
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
PendingIntent pendingIntent = TaskStackBuilder.create(this)
|
||||||
|
.addParentStack(TrackListActivity.class)
|
||||||
.addNextIntent(intent)
|
.addNextIntent(intent)
|
||||||
.getPendingIntent(0, 0);
|
.getPendingIntent(0, pendingIntentFlags);
|
||||||
|
|
||||||
notificationManager.updatePendingIntent(pendingIntent);
|
notificationManager.updatePendingIntent(pendingIntent);
|
||||||
notificationManager.updateContent(getString(R.string.gps_starting));
|
notificationManager.updateContent(getString(R.string.gps_starting));
|
||||||
@@ -416,21 +413,17 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Duration getTotalTime() {
|
|
||||||
return trackRecordingManager.getTrackStatistics().getTotalTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public TrackPointCreator getHandlerServer() {
|
public TrackPointCreator getTrackPointCreator() {
|
||||||
return handlerServer;
|
return trackPointCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<GpsStatusValue> getGpsStatusObservable() {
|
public LiveData<GpsStatusValue> getGpsStatusObservable() {
|
||||||
return gpsStatusObservable;
|
return gpsStatusObservable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<RecordingData> getRecordingDataObservable() {
|
public LiveData<RecordingData> getRecordingDataObservable() {
|
||||||
return recordingDataObservable;
|
return recordingDataObservable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,17 +435,24 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
|
|
||||||
// Compute temporary track statistics using sensorData and update time.
|
// Compute temporary track statistics using sensorData and update time.
|
||||||
|
|
||||||
TrackPointCreator localHandlerServer = this.handlerServer;
|
TrackPointCreator localTrackPointCreator = this.trackPointCreator;
|
||||||
if (localHandlerServer == null) {
|
VoiceAnnouncementManager localVoiceAnnouncementManager = this.voiceAnnouncementManager;
|
||||||
|
if (localTrackPointCreator == null || localVoiceAnnouncementManager == null) {
|
||||||
// when this happens, no recording is running and we should not send any notifications.
|
// 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
|
//TODO This implementation is not a good idea; rather solve the issue for this properly
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair<Track, Pair<TrackPoint, SensorDataSet>> data = trackRecordingManager.get(handlerServer);
|
Pair<Track, Pair<TrackPoint, SensorDataSet>> data = trackRecordingManager.get(trackPointCreator);
|
||||||
|
if (data == null) {
|
||||||
|
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
TrackPoint trackPoint = data.second.first;
|
TrackPoint trackPoint = data.second.first;
|
||||||
egm2008CorrectionManager.correctAltitude(this, trackPoint);
|
egm2008CorrectionManager.correctAltitude(this, trackPoint);
|
||||||
|
|
||||||
|
localVoiceAnnouncementManager.update(data.first);
|
||||||
|
|
||||||
recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second));
|
recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,7 +493,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
private final Track.Id trackId;
|
private final Track.Id trackId;
|
||||||
private final boolean paused;
|
private final boolean paused;
|
||||||
|
|
||||||
public RecordingStatus(Track.Id trackId, boolean paused) {
|
private RecordingStatus(Track.Id trackId, boolean paused) {
|
||||||
this.trackId = trackId;
|
this.trackId = trackId;
|
||||||
this.paused = paused;
|
this.paused = paused;
|
||||||
}
|
}
|
||||||
@@ -514,20 +514,23 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
|||||||
return isRecording() && !isPaused();
|
return isRecording() && !isPaused();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Use
|
private static RecordingStatus notRecording() {
|
||||||
// public RecordingStatus pause() {
|
return new RecordingStatus(null, false);
|
||||||
// return new RecordingStatus(getTrackId(), true);
|
}
|
||||||
// }
|
|
||||||
//
|
private static RecordingStatus record(@NonNull Track.Id trackId) {
|
||||||
// public RecordingStatus record(@NonNull Track.Id trackId) {
|
return new RecordingStatus(trackId, false);
|
||||||
// return new RecordingStatus(trackId, false);
|
}
|
||||||
// }
|
|
||||||
|
private RecordingStatus pause() {
|
||||||
|
return new RecordingStatus(getTrackId(), true);
|
||||||
|
}
|
||||||
|
|
||||||
public RecordingStatus stop() {
|
public RecordingStatus stop() {
|
||||||
return STATUS_DEFAULT;
|
return STATUS_DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "RecordingStatus{" +
|
return "RecordingStatus{" +
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2009 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package de.dennisguse.opentracks.services.handlers;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a simple location listener policy that will always dictate the same polling interval.
|
|
||||||
*
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class AbsoluteLocationListenerPolicy implements LocationListenerPolicy {
|
|
||||||
|
|
||||||
private final Duration interval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param interval the interval to request for gps signal
|
|
||||||
*/
|
|
||||||
public AbsoluteLocationListenerPolicy(Duration interval) {
|
|
||||||
this.interval = interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Duration getDesiredPollingInterval() {
|
|
||||||
return interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinDistance_m() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateIdleTime(Duration idleTime) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||