forked from upstream-mirrors/OpenTracks
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b83f57bd20 |
@@ -0,0 +1,36 @@
|
||||
name: Android Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Android Lint
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'ubuntu:android-24.04'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Lint
|
||||
shell: bash
|
||||
run: ./gradlew lint
|
||||
|
||||
android-check:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: 'ubuntu:android-24.04'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: ./gradlew testNightlyDebugUnitTest --stacktrace
|
||||
@@ -1,56 +0,0 @@
|
||||
name: Auto-Sync Upstream
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 3 * * *' # Uruchamia się codziennie o 3:00 w nocy
|
||||
workflow_dispatch: # Pozwala uruchomić to ręcznie z poziomu Gitea
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync with Upstream
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||
|
||||
- name: Configure Git & Fetch
|
||||
run: |
|
||||
git config user.name "Gitea Actions Bot"
|
||||
git config user.email "actions@gitea.local"
|
||||
git remote add upstream https://gitea.local.labus.uk/upstream-mirrors/OpenTracks.git
|
||||
git fetch upstream main
|
||||
|
||||
- name: Check for updates & Attempt Merge
|
||||
id: merge
|
||||
run: |
|
||||
# Sprawdzamy czy są jakiekolwiek nowe zmiany
|
||||
if git diff --quiet HEAD upstream/main; then
|
||||
echo "Repozytorium jest aktualne. Brak zmian."
|
||||
echo "has_changes=false" >> $GITHUB_ENV
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Znaleziono nowe zmiany. Próbuję scalić..."
|
||||
echo "has_changes=true" >> $GITHUB_ENV
|
||||
|
||||
# Próba automatycznego scalenia (merge)
|
||||
if git merge upstream/main --no-edit; then
|
||||
echo "Scalenie przebiegło pomyślnie!"
|
||||
else
|
||||
echo "Wykryto konflikty! Przerywam scalanie."
|
||||
git merge --abort
|
||||
exit 1 # Błąd celowy - Gitea wyśle Ci powiadomienie o failu
|
||||
fi
|
||||
|
||||
- name: Push changes
|
||||
if: env.has_changes == 'true'
|
||||
run: |
|
||||
git push origin main
|
||||
echo "Zmiany pomyślnie wypchnięte do repozytorium!"
|
||||
@@ -1,78 +0,0 @@
|
||||
name: CI Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Android Lint
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: 'cimg/android:2024.04-node'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Lint
|
||||
shell: bash
|
||||
run: ./gradlew lint
|
||||
|
||||
android-check:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: 'cimg/android:2024.04-node'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: ./gradlew testNightlyDebugUnitTest --stacktrace
|
||||
|
||||
release:
|
||||
name: Build and Upload APK
|
||||
needs: [lint, android-check]
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: 'cimg/android:2024.04-node'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build APKs
|
||||
shell: bash
|
||||
run: |
|
||||
./gradlew assembleDebug
|
||||
./gradlew assembleRelease
|
||||
|
||||
- name: Prepare APKs
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p release-apks
|
||||
find . -name "*.apk" -print -exec cp {} release-apks/ \;
|
||||
ls -la release-apks/
|
||||
|
||||
- name: Upload to Gitea Releases
|
||||
uses: https://gitea.com/actions/gitea-release-action@v1
|
||||
with:
|
||||
tag_name: build-${{ github.run_number }}
|
||||
name: "Build #${{ github.run_number }}"
|
||||
prerelease: true
|
||||
files: |-
|
||||
release-apks/*.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-23
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="OpenTracks">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="androidTest">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="unitTest">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="main">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MarkdownSettings">
|
||||
<option name="previewPanelProviderInfo">
|
||||
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-17
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -45,18 +45,6 @@ Translations are hosted on <a href="https://translate.codeberg.org/engage/opentr
|
||||
<img src="https://translate.codeberg.org/widget/opentracks/opentracks-src-main-res-values-strings-xml-main/horizontal-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
## ⚠️ About This Fork
|
||||
|
||||
This repository is a customized fork of the official OpenTracks application.
|
||||
|
||||
**Core Purpose:**
|
||||
The primary goal of this fork is to fundamentally change how Bluetooth sensors are prioritized over GPS data. In this modified version, Bluetooth sensor data is given absolute priority and actively ignores/overrides GPS signals in most scenarios. This ensures that cadence, speed, and distance measurements strictly rely on hardware sensors rather than falling back to or being averaged with GPS data.
|
||||
|
||||
**Consequential Changes:**
|
||||
As a direct result of these sensor priority changes, several downstream modifications were implemented to support the new data flow:
|
||||
* **UI & Settings:** Expanded settings menus and customized recording layouts to better expose the raw sensor data and give the user finer control.
|
||||
* **Media & Voice Announcements:** Integrated media playback controls directly into the statistics view and overhauled voice announcements to handle audio focus and integrate with the new sensor-first approach.
|
||||
|
||||
## Screenshots
|
||||
|
||||
<div>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* Polar H10
|
||||
* Polar OH1
|
||||
Has updatable firmware that requires a user account at polar.com
|
||||
* Polar Loop Gen 2
|
||||
* 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)
|
||||
* 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)
|
||||
@@ -113,16 +112,3 @@ Here is a list of the smartphones tested:
|
||||
| Google | Nexus 4 | LineageOS 18.1 | Ok |
|
||||
| Samsung | S4 | Android 11 | Ok |
|
||||
| Sony | Xperia XZ1 Compact | LineageOS 16.1 | Failure |
|
||||
|
||||
## Bosch Smart System eBike
|
||||
|
||||
OpenTracks also supports Bosch Smart System eBikes via Bosch's custom BLE service `00000010-eaa2-11e9-81b4-2a2ae2dbcce4`.
|
||||
|
||||
Tested with:
|
||||
|
||||
* Bosch Perfomance Line CX (SW-Version 16.9.0) / Kiox 300 (SW-Version 16.4.0)
|
||||
|
||||
Currently supported measurements:
|
||||
|
||||
* Human power
|
||||
* Cadence
|
||||
|
||||
+23
-30
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:9.4.0'
|
||||
classpath 'com.android.tools.build:gradle:9.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ allprojects {
|
||||
}
|
||||
|
||||
def getVersionName = { ->
|
||||
return 'git describe --tags'.execute(null, project.rootDir).text.trim()
|
||||
return 'git describe --tags'.execute().text.trim()
|
||||
}
|
||||
|
||||
def getVersionCodeNightly = { ->
|
||||
return Integer.valueOf('git rev-list HEAD --count'.execute(null, project.rootDir).text.trim())
|
||||
return Integer.valueOf('git rev-list HEAD --count'.execute().text.trim())
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -33,7 +33,7 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
compileSdk = 37
|
||||
compileSdk = 36
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
@@ -46,14 +46,14 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'de.dennisguse.opentracks.fork'
|
||||
versionCode 6751
|
||||
versionName "v4.28.3"
|
||||
applicationId 'de.dennisguse.opentracks'
|
||||
versionCode 6554
|
||||
versionName "v4.25.1"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
minSdk = 26
|
||||
targetSdk = 37
|
||||
targetSdk = 36
|
||||
|
||||
testInstrumentationRunner 'de.dennisguse.opentracks.TestRunner'
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
@@ -98,7 +98,6 @@ android {
|
||||
release {
|
||||
crunchPngs = false
|
||||
minifyEnabled = false
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,18 +106,20 @@ android {
|
||||
productFlavors {
|
||||
nightly {
|
||||
dimension 'version'
|
||||
applicationId 'de.dennisguse.opentracks.fork.nightly'
|
||||
applicationId 'de.dennisguse.opentracks.nightly'
|
||||
resValue 'string', 'applicationId', 'de.dennisguse.opentracks.nightly'
|
||||
|
||||
versionCode getVersionCodeNightly()
|
||||
versionName getVersionName()
|
||||
base.archivesName = "de.dennisguse.opentracks.fork.nightly_${getVersionCodeNightly()}"
|
||||
base.archivesName = "de.dennisguse.opentracks.nightly_${getVersionCodeNightly()}"
|
||||
|
||||
signingConfig = signingConfigs.nightly
|
||||
}
|
||||
irreproducible {
|
||||
// Non-reproducible: https://f-droid.org/de/packages/de.dennisguse.opentracks/
|
||||
dimension 'version'
|
||||
applicationId 'de.dennisguse.opentracks.fork'
|
||||
applicationId 'de.dennisguse.opentracks'
|
||||
resValue 'string', 'applicationId', 'de.dennisguse.opentracks'
|
||||
versionNameSuffix 'irreproducible'
|
||||
}
|
||||
reproducible {
|
||||
@@ -126,9 +127,10 @@ android {
|
||||
// FDroid: https://f-droid.org/de/packages/de.dennisguse.opentracks.playstore
|
||||
// PlayStore: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
|
||||
dimension 'version'
|
||||
applicationId 'de.dennisguse.opentracks.fork.playstore'
|
||||
applicationId 'de.dennisguse.opentracks.playstore'
|
||||
resValue 'string', 'applicationId', 'de.dennisguse.opentracks.playstore'
|
||||
|
||||
base.archivesName = "de.dennisguse.opentracks.fork.playstore_${getVersionName()}_${android.defaultConfig.versionCode}"
|
||||
base.archivesName = "de.dennisguse.opentracks.playstore_${getVersionName()}_${android.defaultConfig.versionCode}"
|
||||
|
||||
signingConfig = signingConfigs.release
|
||||
}
|
||||
@@ -146,33 +148,24 @@ android {
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'androidx.documentfile:documentfile:1.1.0'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.14.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.2'
|
||||
implementation 'androidx.core:core:1.19.0'
|
||||
implementation 'com.google.android.material:material:1.13.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
||||
implementation 'androidx.core:core:1.17.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.2.0'
|
||||
implementation 'androidx.mediarouter:mediarouter:1.8.1'
|
||||
implementation 'androidx.core:core-location-altitude:1.0.0'
|
||||
implementation 'androidx.work:work-runtime:2.11.2'
|
||||
implementation 'androidx.health.connect:connect-client:1.1.0'
|
||||
implementation 'androidx.core:core-location-altitude:1.0.0-beta01'
|
||||
implementation 'androidx.work:work-runtime:2.11.1'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.7.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.7.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.7.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.21.0'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.6.1'
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
onVariants(selector().all()) { variant ->
|
||||
variant.resValues.put(
|
||||
new com.android.build.api.variant.impl.ResValueKeyImpl("string", "applicationId"),
|
||||
new com.android.build.api.variant.ResValue(variant.applicationId.get(), null)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
(versionName): OpenTracks
|
||||
|
||||
Ændringer:
|
||||
-
|
||||
|
||||
Fejlrettelser:
|
||||
-
|
||||
|
||||
Udvikler:
|
||||
-
|
||||
@@ -1,8 +0,0 @@
|
||||
v4.25.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Restored compatibility with OSMDashboard (Dashboard API protocol version 2)
|
||||
|
||||
Bugfix:
|
||||
- Update UI immediately after resuming a track
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
v4.26.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- GPS is optional: configurable in the settings
|
||||
- BLE sensor address/name is only shown after successful connection
|
||||
|
||||
Bugfix:
|
||||
- Altitude is now shown again in EGM2008 (introduced in v4.25.0)
|
||||
- Altitude and speed were shown as 0.0 briefly when data was stored
|
||||
|
||||
Developer:
|
||||
- Refactored sensor subsystem
|
||||
@@ -1,6 +0,0 @@
|
||||
v4.26.1: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Crash when deleting a UI layout
|
||||
- Crash when heartrate sensor data could not be decoded
|
||||
- Crash when exporting zero tracks
|
||||
@@ -1,5 +0,0 @@
|
||||
v4.26.2: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- GPS via Bluetooth is not (yet) supported
|
||||
- Altitude type (EGM2008) wasn't shown
|
||||
@@ -1,14 +0,0 @@
|
||||
v4.27.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Sensor: power from Bosch eBike
|
||||
- Sensors: include already bonded Bluetooth devices (like Bosch eBike)
|
||||
- Barometer: enforce sampling frequency
|
||||
|
||||
Bugfix:
|
||||
- Dashboard API: did not provide update
|
||||
- GPS could not be enabled with Bluetooth disabled
|
||||
|
||||
Developer:
|
||||
- Removed Mockito
|
||||
- UI tests: less flacky
|
||||
@@ -1,11 +0,0 @@
|
||||
v4.27.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Support for Bosch Smart System eBikes (cadence, human power)
|
||||
- Add activity "gravel biking"
|
||||
|
||||
Bugfix:
|
||||
- While recording: TrackRecordingService would not restart correctly if killed by Android
|
||||
|
||||
Developer:
|
||||
- TargetSDK 37
|
||||
@@ -1,5 +0,0 @@
|
||||
v4.27.2: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Not all valid timezone offsets could be set
|
||||
- Fixed race condition for IDLE trackpoints
|
||||
@@ -1,10 +0,0 @@
|
||||
v4.28.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Support for exporting to Health Connect
|
||||
|
||||
Bugfix:
|
||||
- Dashboard API: when sharing multiple tracks, the currently recorded one wasn't updated
|
||||
- Configuration setting for announcement of lap power wasn't stored correctly (only some languages affected)
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
v4.28.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- HealthConnect: sending total distance, speed and elevation gain
|
||||
|
||||
Bugfix:
|
||||
- Gravel activity was not selectable
|
||||
|
||||
Developer:
|
||||
- Cleanup ContentProvider URLs
|
||||
@@ -1,7 +0,0 @@
|
||||
v4.28.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Import: ignore GPX waypoints without time
|
||||
|
||||
Bugfix:
|
||||
- Only start GPS once
|
||||
@@ -1,10 +0,0 @@
|
||||
v4.28.3: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Settings: allow to disable segment creation (max recording distance)
|
||||
- Importer: match activity type case-insensitive
|
||||
- GPX: export moving speed in summary
|
||||
|
||||
Bugfix:
|
||||
- GPX: importer must respect namespace prefixes
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
v3.0.0: OpenTracks
|
||||
|
||||
Primera versión de OpenTracks (es decir, una bifurcación dura de MyTracks)
|
||||
Primer lanzamiento de OpenTracks (es decir, una bifurcación de MyTracks)
|
||||
|
||||
Cambios:
|
||||
- Cambio de nombre de MyTracks a OpenTracks
|
||||
- Se ha añadido el sensor de frecuencia cardíaca Bluetooth LE
|
||||
- Se han eliminado todas las funciones que requerían acceso a Internet
|
||||
- Se ha eliminado la integración con Google Maps
|
||||
- Se ha eliminado el uso de los servicios de Google Play
|
||||
- Se han eliminado los sensores ANT+
|
||||
- Se han eliminado los sensores Bluetooth clásicos
|
||||
- Se ha eliminado Protobuf
|
||||
- Se han eliminado la estimación de calorías y la estimación de actividad
|
||||
- Se ha eliminado la API del servicio MyTracks (es decir, no hay integración con otras aplicaciones)
|
||||
- Se agregó un sensor de frecuencia cardíaca Bluetooth LE
|
||||
- Se eliminaron todas las funciones que requieren acceso a Internet.
|
||||
- Se eliminó la integración de Google Maps
|
||||
- Se eliminó el uso de los servicios de Google Play.
|
||||
- Se eliminaron los sensores ANT+
|
||||
- Se eliminaron los sensores clásicos de Bluetooth.
|
||||
- Se eliminó Protobuf
|
||||
- Se eliminaron la estimación de calorías y la estimación de actividad.
|
||||
- Se eliminó la API del servicio MyTracks (es decir, no hay integración con otras aplicaciones)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
v3.0.1: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Compartir marcadores
|
||||
- Configuración reestructurada
|
||||
- Se ha eliminado el botón «Atrás» de la barra de acciones
|
||||
- Exportación: se ha eliminado la opción de configuración del tamaño de las fotos
|
||||
- Exportación: ahora se puede seleccionar el formato KML
|
||||
|
||||
Correcciones de errores:
|
||||
- El directorio de exportación es ahora /OpenTracks/
|
||||
- El modo horizontal ya no provoca fallos
|
||||
- Se ha corregido la exportación a KML/KMZ
|
||||
|
||||
Desarrollador:
|
||||
- Las pruebas se pueden ejecutar (aunque muchas fallan)
|
||||
- Se ha habilitado la integración continua con TravisCI (sin pruebas)
|
||||
@@ -1,14 +0,0 @@
|
||||
v3.1.0: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Se ha añadido la opción de mostrar el recorrido en aplicaciones de mapas (probado con OsmAnd y Maps.ME)
|
||||
- Se ha añadido la opción de mostrar las estadísticas del recorrido sin desbloquear el dispositivo mientras se realiza el seguimiento
|
||||
- Se ha eliminado el modo horizontal para la vista detallada del recorrido (presentaba errores)
|
||||
- Se ha eliminado el historial de sugerencias de búsqueda
|
||||
|
||||
Corrección de errores:
|
||||
- Se solicita permiso de almacenamiento para «Importar todo»/«Exportar todo»
|
||||
|
||||
Desarrollador:
|
||||
- Actualización a Gradle 5.6
|
||||
- No se minimizan las compilaciones de lanzamiento
|
||||
@@ -1,16 +0,0 @@
|
||||
v3.2.0: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Se ha eliminado el widget
|
||||
- Se han incorporado iconos de Material Design
|
||||
- Se han eliminado el snowboard y el esquí (se necesitan nuevos iconos)
|
||||
|
||||
Corrección de errores:
|
||||
- Se ha corregido la funcionalidad de búsqueda
|
||||
|
||||
Desarrollador:
|
||||
- Se han corregido (algunos) casos de prueba
|
||||
- Se ha eliminado la funcionalidad «parcelable» en desuso
|
||||
- Limpieza y reformateo a fondo
|
||||
- No se utiliza la difusión para la comunicación entre la aplicación y el widget
|
||||
- El archivo stats_content.xml no incluye otros archivos
|
||||
@@ -1,7 +0,0 @@
|
||||
v3.2.1: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Los ajustes ahora están en una sola página
|
||||
|
||||
Desarrollo:
|
||||
-Los ajustes se migraron a Androidx compat (usa fragments)
|
||||
@@ -1,13 +0,0 @@
|
||||
v3.2.2: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Se eliminaron las estadísticas agregadas
|
||||
- Migrado a Material Design
|
||||
|
||||
Corrección de errores:
|
||||
- Se corrigió la exportación de datos de sensores
|
||||
- Se corrigió la importación de datos de sensores incompletos
|
||||
|
||||
Desarrollador:
|
||||
- Se reemplazó la barra de acción (actionbar) por la barra de herramientas (toolbar)
|
||||
- Migrado a androidx appcompat (incl. Material Design)
|
||||
@@ -1,14 +0,0 @@
|
||||
v3.2.3: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Compartir: usar Android Sharesheet
|
||||
- Compartir: compartir múltiples pistas como archivos individuales
|
||||
- Usar Android ShareSheet
|
||||
- Sensor de frecuencia cardíaca (Bluetooth LE): reconexión automática
|
||||
|
||||
Corrección de errores:
|
||||
- Sensor de frecuencia cardíaca (Bluetooth LE): desconexión correcta
|
||||
- La frecuencia cardíaca no siempre se mostraba durante la grabación
|
||||
|
||||
Desarrollador:
|
||||
- Se habilitaron las pruebas de instrumentación en TravisCI
|
||||
@@ -1,5 +0,0 @@
|
||||
v3.2.4: OpenTracks
|
||||
|
||||
Corrección de errores:
|
||||
- Caída/cierre inesperado (crash) en la actividad de ajustes
|
||||
Se eliminó la posibilidad de cambiar la frecuencia de voz/fracción desde el menú contextual
|
||||
@@ -1,15 +0,0 @@
|
||||
v3.2.5: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Deshabilitar el cambio de actividad
|
||||
- Añadir permiso para Android 10: grabación de datos de ubicación
|
||||
|
||||
Corrección de errores:
|
||||
- Corregir el restablecimiento de los ajustes
|
||||
|
||||
Desarrollador:
|
||||
- Preferencias
|
||||
- si no se pueden cargar los Ajustes, restablecer todas las preferencias
|
||||
- se movió el acceso a las preferencias a PreferencesUtil
|
||||
- se movieron los valores por defecto de las preferencias del código a XML
|
||||
- usar SummaryProvider
|
||||
@@ -1,8 +0,0 @@
|
||||
v3.2.6: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Monitor de frecuencia cardíaca: escanear dispositivos disponibles
|
||||
- Eliminar acceso directo a los ajustes de Bluetooth
|
||||
|
||||
Desarrollador:
|
||||
- Preferencias: se movieron los valores por defecto de las preferencias del código a XML
|
||||
@@ -1,10 +0,0 @@
|
||||
v3.2.7: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- La notificación muestra la precisión del GPS
|
||||
- Mostrar notificación emergente (heads-up) si la precisión del GPS cae por debajo del umbral
|
||||
- Mostrar siempre el gráfico tanto por tiempo como por distancia
|
||||
- Se añadió ayuda (en Ajustes)
|
||||
|
||||
Corrección de errores:
|
||||
- TrackDetailActivity: se deshabilitó el modo horizontal que no funcionaba (temporal)
|
||||
@@ -1,10 +0,0 @@
|
||||
v3.2.8: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Gráfico: zoom mediante gestos
|
||||
|
||||
Corrección de errores:
|
||||
- La exportación a través de KMZ no exportaba correctamente los nombres de archivo de las imágenes
|
||||
- Compartir múltiples pistas no incluía los archivos KMZ
|
||||
- No se mostraba la interfaz de usuario de MarkerDetailActivity
|
||||
- Los ajustes se cerraban inesperadamente si la preferencia de frecuencia cardíaca no estaba inicializada
|
||||
@@ -1,10 +0,0 @@
|
||||
v3.2.9: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Opción de mantener la pantalla encendida durante la grabación
|
||||
- Marcadores: se eliminaron por completo los marcadores de división (split markers)
|
||||
- Los anuncios de voz ahora funcionan sin el permiso READ_PHONE_STATE
|
||||
|
||||
Corrección de errores:
|
||||
- Estadísticas: no se mostraba la frecuencia cardíaca
|
||||
- Estadísticas: el tiempo se restablecía brevemente a 0s
|
||||
@@ -1,11 +0,0 @@
|
||||
v3.2.10: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- ChartView ya no es configurable
|
||||
- Después de reiniciar: se eliminó el reinicio automático de una grabación incompleta
|
||||
|
||||
Corrección de errores:
|
||||
- Los anuncios de voz usan MAY_DUCK (de nuevo)
|
||||
- ChartView muestra marcadores (de nuevo)
|
||||
- Se corrigió el diseño de grabación en horizontal (landscape recordingLayout) de TrackListActivity
|
||||
- Se solicita el comportamiento heredado de la API de archivos (necesario para Android 10)
|
||||
@@ -1,12 +0,0 @@
|
||||
v3.2.11: OpenTracks
|
||||
|
||||
Error:
|
||||
- La velocidad máxima es idéntica a la velocidad promedio
|
||||
|
||||
Corrección de errores:
|
||||
- Cierre inesperado (crash) al seleccionar el sensor de frecuencia cardíaca
|
||||
|
||||
Desarrollador:
|
||||
- Se reemplazó TabHost por TabsLayout
|
||||
- Se eliminó el primer punto de ruta (waypoint) especial
|
||||
- Se eliminaron las traducciones duplicadas
|
||||
@@ -1,13 +0,0 @@
|
||||
v3.3.0: OpenTracks
|
||||
|
||||
Cambios importantes:
|
||||
- Fotos en almacenamiento privado (ocultas para otras apps)
|
||||
NOTA: No se verán fotos de versiones anteriores
|
||||
- Español (Latinoamérica) requiere Android 7.0 o superior
|
||||
|
||||
Cambios:
|
||||
- Importar/exportar vía Storage Access Framework (sin permisos de almacenamiento)
|
||||
|
||||
Desarrollador:
|
||||
- Limpieza de estructuras de datos internas
|
||||
- Icono/nombre distintos para versión debug
|
||||
@@ -1,11 +0,0 @@
|
||||
v3.3.1: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Android 10: permite conservar datos al desinstalar
|
||||
- API Dashboard: icono de mapa para abrir la pista en app externa (ej. OSMDashboard)
|
||||
|
||||
Corrección de errores:
|
||||
- Se corrigió el nombre de la app (roto en v3.3.0)
|
||||
|
||||
Desarrollador:
|
||||
- Documentación de la API Dashboard en README_DEVELOPER.md
|
||||
@@ -1,11 +0,0 @@
|
||||
v3.3.2: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Exportación: ahora sobrescribe los archivos
|
||||
|
||||
Corrección de errores:
|
||||
- Se elimina siempre la notificación de grabación
|
||||
|
||||
Desarrollador:
|
||||
- En API 26+ se usa java.time para analizar y escribir fechas
|
||||
- Refactorización de TrackRecordingService
|
||||
@@ -1,12 +0,0 @@
|
||||
v3.4.0: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- API Dashboard: múltiples pistas
|
||||
- Ayuda actualizada
|
||||
|
||||
Corrección de errores:
|
||||
- Se corrigió el cierre inesperado al recrear fragmentos
|
||||
- En pausa: evita que la app se detenga
|
||||
|
||||
Desarrollador:
|
||||
- Pruebas habilitadas vía CI (Github Actions y TravisCI)
|
||||
@@ -1,14 +0,0 @@
|
||||
v3.5.0: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Marcador: seleccionar imágenes de la galería
|
||||
- Marcador: compartir imagen
|
||||
- Utilizar el nombre de la ruta como nombre de archivo al compartir
|
||||
- Se han eliminado los valores mínimos y máximos de altitud (eran imprecisos)
|
||||
|
||||
Corrección de errores:
|
||||
- Los gráficos utilizan las unidades y la escala preferidas
|
||||
|
||||
Desarrollador:
|
||||
- Base de datos: eliminar columnas en desuso
|
||||
- Base de datos: añadir índices
|
||||
@@ -1,10 +0,0 @@
|
||||
v3.6.0: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Ahora se pueden reanudar las rutas que se habían detenido
|
||||
|
||||
Corrección de errores:
|
||||
- La importación de archivos KMZ no procesaba correctamente la URL de las fotos
|
||||
|
||||
Desarrollador:
|
||||
- Se ha simplificado el procesamiento de datos
|
||||
@@ -1,4 +0,0 @@
|
||||
v3.6.1: OpenTracks
|
||||
|
||||
Corrección de errores:
|
||||
- Importación de KMZ: guardar la ruta correcta de la imagen
|
||||
@@ -1,4 +0,0 @@
|
||||
v3.6.2: OpenTracks
|
||||
|
||||
Corrección de errores:
|
||||
- Los gráficos no se mostraban correctamente (problema introducido en la v3.6.0)
|
||||
@@ -1,13 +0,0 @@
|
||||
v3.7.0: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Informe de velocidad/ritmo por tipo de actividad
|
||||
- Se ha añadido una solución provisional para el cambio de semana del GPS
|
||||
- (Experimental) Compatibilidad con sensores de ciclismo Bluetooth LE (cadencia y velocidad)
|
||||
- Se ha mejorado la importación de KMZ/KML
|
||||
|
||||
Corrección de errores:
|
||||
- Importación de KMZ: solo se importan las imágenes utilizadas
|
||||
|
||||
Desarrollador:
|
||||
- Se ha refactorizado TrackRecordingService
|
||||
@@ -1,7 +0,0 @@
|
||||
v3.7.1: OpenTracks
|
||||
|
||||
Solución provisional:
|
||||
- Ocultar el botón de inicio del GPS
|
||||
|
||||
Corrección de errores:
|
||||
- Se ha corregido un puntero nulo
|
||||
@@ -1,6 +0,0 @@
|
||||
v3.7.2: OpenTracks
|
||||
|
||||
Corrección de errores:
|
||||
- Se ha vuelto a añadir el botón «Iniciar GPS»
|
||||
- El nombre de la ruta ya no se sobrescribe al final de una grabación
|
||||
- Marcadores: al eliminar una foto, también se borra el archivo
|
||||
@@ -1,7 +0,0 @@
|
||||
v3.7.3: OpenTracks
|
||||
|
||||
Corrección de errores:
|
||||
- Se ha corregido el cálculo de la cadencia y la velocidad (mediante el sensor Bluetooth LE)
|
||||
|
||||
Desarrollador:
|
||||
- Se han eliminado las traducciones que no se utilizaban
|
||||
@@ -1,12 +0,0 @@
|
||||
v3.7.4: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Formato de compartir/exportar configurable
|
||||
|
||||
Corrección de errores:
|
||||
- El diámetro de la rueda no se almacenaba correctamente (para el sensor de velocidad)
|
||||
- Ayuda: los enlaces son clicables
|
||||
|
||||
Desarrollador:
|
||||
- Se ha dividido TrackDetailActivity
|
||||
- El sucesor de StatsFragment utiliza ConstraintLayout
|
||||
@@ -1,11 +0,0 @@
|
||||
v3.7.5: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Se ha añadido un resumen de estadísticas agregadas (desde el menú de la pantalla principal)
|
||||
- API del panel de control: se pasan los ajustes «mostrar en la pantalla de bloqueo» y «mantener la pantalla encendida»
|
||||
- Se han actualizado las traducciones
|
||||
- Importación: se ha flexibilizado el formato de fecha ISO 8601
|
||||
- Mostrar ruta en el mapa: se puede seleccionar el formato de archivo y la apertura del panel de control funciona en Android R (API 30+)
|
||||
|
||||
Corrección de errores:
|
||||
- El icono de la aplicación ahora se muestra correctamente
|
||||
@@ -1,11 +0,0 @@
|
||||
v3.7.6: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Modo horizontal
|
||||
- La interfaz de usuario para borrar rutas ahora es solo un indicador giratorio
|
||||
- La velocidad se exporta e importa (GPX/KML/KMZ)
|
||||
- El formato de exportación predeterminado es KMZ, incluyendo todos los datos y las imágenes
|
||||
|
||||
Desarrollador:
|
||||
- Se han sustituido las AsyncTasks por hilos
|
||||
- Sincronización: se genera un UUID por cada ruta (aún no se exporta)
|
||||
@@ -1,16 +0,0 @@
|
||||
v3.8.0: OpenTracks
|
||||
|
||||
Cambio importante:
|
||||
- Importación/exportación de GPX de velocidad, cadencia y frecuencia cardíaca a través de TrackPointExtension/v2
|
||||
|
||||
Cambios:
|
||||
- API del panel de control: permite acceder a los marcadores (incluida una llamada de retorno para mostrarlos en OpenTracks)
|
||||
- Nuevo icono de marcador (chincheta naranja, SVG)
|
||||
- Los archivos que se van a importar se pueden seleccionar a través del gestor de archivos
|
||||
|
||||
Correcciones de errores:
|
||||
- La interfaz de usuario de estadísticas agregadas respeta la frecuencia preferida
|
||||
- Fallaba la edición de un marcador al cambiar la orientación
|
||||
|
||||
Desarrollador:
|
||||
- Se ha refactorizado TrackRecordingService
|
||||
@@ -1,8 +0,0 @@
|
||||
v3.8.1: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Durante la grabación: se ha añadido la opción de pantalla completa
|
||||
- El panel de control se inicia en la misma tarea
|
||||
|
||||
Correcciones de errores:
|
||||
- Los filtros de intención para importar archivos eran demasiado optimistas
|
||||
@@ -1,14 +0,0 @@
|
||||
v3.9.0: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Exportación de rutas con su UUID
|
||||
- Se evita la importación de rutas ya existentes por su UUID (configurable; por defecto, activado)
|
||||
- Ahora hay que mantener pulsado el botón de pausa/parada
|
||||
- El preinicio del GPS muestra el estado de la señal
|
||||
- Se han añadido nuevos iconos de actividad
|
||||
- Sensor de velocidad: ahora se puede configurar el tamaño de la rueda en milímetros
|
||||
|
||||
Correcciones de errores:
|
||||
- Se han corregido algunos fallos poco frecuentes (NPE)
|
||||
- Se ha corregido el formato del ritmo
|
||||
- Se ha cambiado la posición del icono de la aplicación en el lanzador
|
||||
@@ -1,7 +1,7 @@
|
||||
v4.14.1: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Se puede desactivar la detección de inactividad
|
||||
- Detección de inactividad se puede desactivar
|
||||
|
||||
Corrección de errores:
|
||||
- La interfaz de usuario para añadir imágenes a los marcadores estaba oculta
|
||||
Correcciones de errores:
|
||||
- UI para añadir imágenes a los marcadores estaba oculto
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
v4.27.0: OpenTracks
|
||||
|
||||
Cambios:
|
||||
- Sensor: potencia de la bicicleta eléctrica Bosch
|
||||
- Sensores: se incluyen los dispositivos Bluetooth ya emparejados (como la bicicleta eléctrica Bosch)
|
||||
- Barómetro: se aplica la frecuencia de muestreo
|
||||
|
||||
Corrección de errores:
|
||||
- API del panel de control: no se proporcionaba la actualización
|
||||
- No se podía activar el GPS con el Bluetooth desactivado
|
||||
|
||||
Desarrollador:
|
||||
- Se ha eliminado Mockito
|
||||
- Pruebas de la interfaz de usuario: menos inestables
|
||||
@@ -1,22 +1,8 @@
|
||||
Un compañero de seguimiento deportivo que respeta tu privacidad.
|
||||
OpenTracks es un tracker deportivo que respeta tu privacidad.
|
||||
|
||||
El deporte y las actividades al aire libre aportan felicidad, destreza y confianza.
|
||||
|
||||
Cuida tu salud llevando un registro de tu entrenamiento.
|
||||
Registra tus sesiones de carrera o caminata, y te ofrece un ciclocomputador con una pantalla más grande para el ciclismo.
|
||||
Marca lugares interesantes a lo largo de tu recorrido con fotos.
|
||||
Guarda estadísticas detalladas para su análisis.
|
||||
Comparte solo los datos que quieras que vean los demás.
|
||||
|
||||
* Avisos de voz.
|
||||
* Compatible con sensores Bluetooth LE: frecuencia cardíaca, velocidad y distancia (ciclismo), cadencia (ciclismo) y medidor de potencia (ciclismo).
|
||||
* Desnivel positivo y negativo: mediante sensor barométrico.
|
||||
* Altitud mostrada en EGM2008 (por encima del nivel medio del mar); exportada como WGS84.
|
||||
* Exporta los datos como rutas en formato KMZ (incluidas fotos), KML o GPX.
|
||||
* No requiere acceso a Internet ni permisos adicionales.
|
||||
* Temas oscuro y claro, respetando la configuración del sistema.
|
||||
* Sin anuncios.
|
||||
|
||||
Software libre / Software gratuito / Código abierto,
|
||||
lo que significa que puedes utilizar, estudiar, modificar y compartir el código fuente.
|
||||
Licencia Apache 2.0
|
||||
Funciones:
|
||||
* Tracker deportivo: haz un seguimiento de tus actividades al aire libre
|
||||
* Marca lugares de interés, incluso agrega fotos a los marcadores
|
||||
* Pulsómetro (compatible con dispositivos Bluetooth LE)
|
||||
* Exporta tus rutas como KML o GPX
|
||||
* Completamente offline, no necesitas internet
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
v4.26.2: OpenTracks
|
||||
|
||||
Arranxos:
|
||||
- (aínda) non hai compatibilidade co GPS vía Bluetooth
|
||||
- non se mostraba a altitude tipo (EGM2008)
|
||||
@@ -1,15 +0,0 @@
|
||||
v3.0.0: OpenTracks
|
||||
|
||||
Rilisan pertama OpenTracks (fork keras dari MyTracks)
|
||||
|
||||
Perubahan:
|
||||
- Perubahan merek MyTracks ke OpenTracks
|
||||
- Menambahkan sensor detak jantung Bluetooth LE
|
||||
- Menghapus semua fitur yang memerlukan koneksi internet
|
||||
- Menghapus integrasi Google Maps
|
||||
- Menghapus penggunaan layanan Google Play
|
||||
- Menghapus sensor ANT+
|
||||
- Menghapus sensor klasik Bluetooth
|
||||
- Menghapus Protobuf
|
||||
- Menghapus estimasi kalori dan aktivitas
|
||||
- Menghapus API MyTracks Service (dengan kata lain, tidak ada integrasi dengan aplikasi lain)
|
||||
@@ -1,17 +0,0 @@
|
||||
v3.0.1: OpenTracks
|
||||
|
||||
Perubahan:
|
||||
- Bagikan penanda
|
||||
- Perubahan struktur pengaturan
|
||||
- Menghapus tombol kembali di bilah aksi
|
||||
- Ekspor: menghapus pilihan pengaturan ukuran foto
|
||||
- Ekspor: KML sebagai format sekarang dapat dipilih
|
||||
|
||||
Perbaikan Bug:
|
||||
- direktori ekspor sekarang di /OpenTracks/
|
||||
- mode lanskap tidak akan mogok
|
||||
- memperbaiki ekspor KML/KMZ
|
||||
|
||||
Pengembang:
|
||||
- Tes dapat dieksekusi (namun banyak yang gagal)
|
||||
- Mengaktifkan integrasi terus menerus dengan TravisCI (tanpa tes)
|
||||
@@ -1,14 +0,0 @@
|
||||
v3.1.0: OpenTracks
|
||||
|
||||
Perubahan:
|
||||
- Menambahkan opsi tampilan jalur salju di peta aplikasi (telah diuji dengan OsmAnd dan Maps.ME)
|
||||
- Menambahkan ketika melacak, akan menampilkan statistik jalur tanpa perlu membuka kunci perangkat
|
||||
- Menghapus mode landkap untuk tampilan detail jalur (tidak stabil)
|
||||
- Menghapus riwayat saran penelusuran
|
||||
|
||||
Perbaikan Bug:
|
||||
- Permintaan izin penyimpanan untuk impor/ekspor semua
|
||||
|
||||
Pengembang:
|
||||
- Pembaruan ke Gradle 5.6
|
||||
- Jangan mengecilkan (minify) rilisan build
|
||||
@@ -1,4 +1,4 @@
|
||||
Sebuah teman pelacak olahraga yang benar-benar menghormati privasi Anda.
|
||||
Teman pelacakan olahraga yang menghormati privasi Anda.
|
||||
|
||||
Olahraga dan aktivitas luar ruangan memberikan kebahagiaan, keterampilan, dan kepercayaan diri.
|
||||
|
||||
@@ -11,12 +11,12 @@ Hanya membagikan data yang Anda ingin orang lain memilikinya.
|
||||
* Notifikasi suara.
|
||||
* Mendukung sensor Bluetooth LE: detak jantung, kecepatan dan jarak (bersepeda), irama (bersepeda), dan pengukur daya (bersepeda).
|
||||
* Kenaikan dan penurunan ketinggian: melalui sensor barometrik.
|
||||
* Ketinggian ditampilkan dalam EGM2008 (di atas permukaan laut); diekspor sebagai WGS84.
|
||||
* Ketinggian yang ditampilkan dalam EGM2008 (di atas permukaan laut); diekspor sebagai WGS84.
|
||||
* Mengekspor data sebagai lintasan, baik sebagai KMZ (termasuk foto), KML, atau GPX.
|
||||
* Tidak ada akses Internet atau izin tambahan.
|
||||
* Tema gelap dan terang, dengan memperhatikan pengaturan sistem.
|
||||
* Tanpa iklan.
|
||||
|
||||
Perangkat Lunak Bebas / Sumber Terbuka
|
||||
Perangkat lunak bebas / Perangkat Lunak Bebas / Sumber Terbuka
|
||||
yang berarti Anda dapat menggunakan, mempelajari, mengubah, dan berbagi kode sumber.
|
||||
Berlisensi Apache 2.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
Sebuah teman pelacak olahraga yang benar-benar menghormati privasi Anda.
|
||||
Sebuah pelacak olahraga yang benar-benar menghormati privasi Anda.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
v4.26.2: OpenTracks
|
||||
|
||||
Correzioni:
|
||||
- Il GPS via Bluetooth non è (ancora) supportato
|
||||
- Il tipo di altitudine (EGM2008) non viene mostrato
|
||||
@@ -1,22 +0,0 @@
|
||||
Un tracker sportivo che rispetta la tua privacy.
|
||||
|
||||
Sport e attività all'aria aperta portano a felicità, allenamento e fiducia
|
||||
|
||||
Valorizza la tua salute tenendo traccia dei tuoi progressi.
|
||||
Registra quando stai correndo o passeggiando e regalati un monitor più grande quando fai ciclismo.
|
||||
Segna con fotografie i posti interessanti incrociati durante il tragitto.
|
||||
Tieni traccia delle statistiche in dettaglio per analizzarle.
|
||||
Condividi solo i dati che vuoi.
|
||||
|
||||
* Annunci vocali.
|
||||
* Supporto a sensori Bluetooth LE: battito cardiaco, velocità e distanza (ciclismo), cadenza (ciclismo), rilevamento potenza (ciclismo).
|
||||
* Altitudine positiva o negativa tramite sensore barometrico.
|
||||
* Altitudine mostrata in EGM2008 (sopra il livello del mare) ed esportata come WGS84.
|
||||
* Esporta tracce come KMZ (con foto incluse), KML o GPX.
|
||||
* Non richiede accesso a internet o altri permessi.
|
||||
* Temi chiaro e scuro, o seguendo la impostazioni di sistema.
|
||||
* Nessuna pubblicità.
|
||||
|
||||
Libre software / Free Software / Open Source
|
||||
cioè puoi usare, studiare, cambiare e condividere il codice sorgente.
|
||||
Licenza: Apache 2.0
|
||||
@@ -1,22 +1,11 @@
|
||||
Um companheiro de rastreamento esportivo que respeita sua privacidade.
|
||||
OpenTracks é um aplicativo de rastreamento de esportes que respeita totalmente a sua privacidade.
|
||||
|
||||
Esportes e atividades ao ar livre garantem felicidade, habilidades e confiança.
|
||||
|
||||
Valoriza sua saúde acompanhando os seus treinos.
|
||||
Grava enquanto você corre ou caminha e te entrega um computador de bicicleta com uma tela maior para o ciclismo.
|
||||
Marca locais interessantes durante o percurso com fotos.
|
||||
Deixa estatísticas registradas em grande detalhe para analises.
|
||||
Compartilha apenas os dados que você quer que outros tenham.
|
||||
|
||||
* Anúncios por voz.
|
||||
* Suporte para sensores Bluetooth LE: frequência cardíaca, velocidade e distância (ciclismo), cadência (ciclismo) e medição de potência (ciclismo).
|
||||
* Ganhos e perdas de altitude: via sensor barométrico.
|
||||
* Altitudes exibidas em EGM2008 (acima do nível médio do mar); exportado como WGS84.
|
||||
* Exporta os dados de rastreio como KMZ (inclusive fotos), KML ou GPX.
|
||||
* Sem acesso à internet ou permissões extra.
|
||||
* Tema claro e escuro, respeitando as configurações do sistema.
|
||||
* Sem propagandas.
|
||||
|
||||
Software Livre / Código Aberto
|
||||
Significa que você pode usar, estudar, mudar e compartilhar o código.
|
||||
Licença Apache 2.0
|
||||
Características:
|
||||
* Rastreamento de esportes: monitore seu esporte e atividades ao ar livre
|
||||
* Fotos e marcadores: marque locais interessantes durante o rastreamento
|
||||
* Ganho e perda de altitude: via sensor barométrico
|
||||
* Altitude mostrada no EGM2008 (acima do nível médio do mar); exportado como WGS84
|
||||
* Anúncios de voz
|
||||
* Suporta sensores Bluetooth LE: frequência cardíaca, velocidade e distância (ciclismo), cadência (ciclismo) e medidor de potência (ciclismo)
|
||||
* Exportar dados: exporte trilhas como KMZ (incluindo fotos), KML, ou GPX
|
||||
* Sem acesso à Internet: a Internet não é usada
|
||||
|
||||
@@ -1 +1 @@
|
||||
一款尊重您隱私的運動蹤跡紀錄器。
|
||||
一個尊重您的隱私的運動蹤跡紀錄器。
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/491f83666ae7f4d6ebb28fee72ebb035/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/0d1a1acdc708062093673f65aa9aba4b/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/491f83666ae7f4d6ebb28fee72ebb035/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/0d1a1acdc708062093673f65aa9aba4b/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/7083b89563e7ce20943037b8cd2b8cc2/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/060bbb778a1f55ea705fdebd2ccfeab9/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/491f83666ae7f4d6ebb28fee72ebb035/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/0d1a1acdc708062093673f65aa9aba4b/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/d09679dc60fe5aa05ef7d03efdefac20/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/ed4e3bf2f5e7c5d9aabc4cbd8acd555e/redirect
|
||||
toolchainVendor=JETBRAINS
|
||||
toolchainVersion=21
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
||||
cat << 'EOC' > src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java.patch
|
||||
--- src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
|
||||
+++ src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
|
||||
@@ -351,6 +351,11 @@
|
||||
public static boolean shouldVoiceAnnouncementLongBreak() {
|
||||
return getBoolean(R.string.settings_announcements_long_break_key, true);
|
||||
}
|
||||
+
|
||||
+ public static long getLongBreakDuration() {
|
||||
+ int minutes = getInt(R.string.settings_long_break_duration_key, 3);
|
||||
+ return minutes * 60 * 1000L;
|
||||
+ }
|
||||
EOC
|
||||
patch -p0 < src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java.patch
|
||||
@@ -1,13 +0,0 @@
|
||||
cat << 'EOC' > src/main/res/values/strings.xml.patch
|
||||
--- src/main/res/values/strings.xml
|
||||
+++ src/main/res/values/strings.xml
|
||||
@@ -1071,6 +1071,8 @@
|
||||
|
||||
<string name="settings_announcements_long_break_key">settings_announcements_long_break_key</string>
|
||||
<string name="settings_announcements_long_break_title">Long break</string>
|
||||
+ <string name="settings_long_break_duration_key">settings_long_break_duration_key</string>
|
||||
+ <string name="settings_long_break_duration_title">Long break duration (minutes)</string>
|
||||
|
||||
</resources>
|
||||
EOC
|
||||
patch -p0 < src/main/res/values/strings.xml.patch
|
||||
@@ -1 +0,0 @@
|
||||
cat src/main/java/de/dennisguse/opentracks/data/statistics/TrackStatisticsUpdater.java | grep -n "if (movingDistance != null) {" -B 2 -A 30
|
||||
@@ -1,14 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
<h1>OpenTracks: Privacy Policy</h1>
|
||||
|
||||
<p>
|
||||
OpenTracks does only store data on the local device that is relevant for tracking your sport exercise.
|
||||
Stored data is not transmitted from the app itself to a third party.
|
||||
|
||||
Data is stored by OpenTracks until:
|
||||
1. the user deletes the recorded track or
|
||||
2. the user uninstalls OpenTracks
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +0,0 @@
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||
}
|
||||
rootProject.name = 'OpenTracks'
|
||||
@@ -2,117 +2,76 @@ package de.dennisguse.opentracks;
|
||||
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.longClick;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
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.withParentIndex;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static androidx.test.espresso.matcher.RootMatchers.isDialog;
|
||||
import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.performClick;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.performClickOnAncestor;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.performLongClickOnAncestor;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitUntilDisplayed;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitUntilRecyclerViewItemCount;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.withRecyclerViewItemCount;
|
||||
import static de.dennisguse.opentracks.util.TestEnvironmentUtils.resetTrackRecordingServiceAndDeleteTracks;
|
||||
import static de.dennisguse.opentracks.util.TrackUiTestUtils.renameStoppedTrack;
|
||||
import static de.dennisguse.opentracks.util.TrackUiTestUtils.startRecordingFromTrackList;
|
||||
import static de.dennisguse.opentracks.util.TrackUiTestUtils.stopRecordingToTrackStopped;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.childAtPosition;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoDeleteTrackTest {
|
||||
|
||||
private static final Duration UI_TIMEOUT = Duration.ofSeconds(10);
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
resetTrackRecordingServiceAndDeleteTracks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteTrackFromTrackStoppedActivity() {
|
||||
recordTrackAndOpenTrackStoppedActivity();
|
||||
public void espressoDeleteTrackTest() {
|
||||
// TrackListActivity: start recording
|
||||
onView(withId(R.id.track_list_fab_action))
|
||||
.perform(click());
|
||||
|
||||
// TrackStoppedActivity: discard the just-recorded track directly
|
||||
onView(withId(R.id.discard_button))
|
||||
.perform(performClick());
|
||||
onView(withText(android.R.string.ok))
|
||||
.inRoot(isDialog())
|
||||
.perform(performClick());
|
||||
waitUntilDisplayed(withId(R.id.track_list), UI_TIMEOUT);
|
||||
waitUntilRecyclerViewItemCount(withId(R.id.track_list), 0, UI_TIMEOUT);
|
||||
|
||||
// the recycler is empty again after discarding the only recorded track
|
||||
onView(withId(R.id.track_list))
|
||||
.check(matches(withRecyclerViewItemCount(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteTrackFromSelectedTrackInTrackList() {
|
||||
String uniqueTrackName = "DeleteFromTrackList-" + System.currentTimeMillis();
|
||||
|
||||
recordTrackAndOpenTrackStoppedActivity();
|
||||
renameStoppedTrack(uniqueTrackName, UI_TIMEOUT);
|
||||
// TrackRecordingActivity
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
// wait; stay recording
|
||||
.perform(waitFor(5000))
|
||||
// stop;
|
||||
.perform(longClick());
|
||||
|
||||
// TrackStoppedActivity
|
||||
onView(withId(R.id.finish_button))
|
||||
.perform(performClick());
|
||||
waitUntilDisplayed(withId(R.id.track_list), UI_TIMEOUT);
|
||||
waitUntilDisplayed(allOf(withId(R.id.track_list_item_name), withText(uniqueTrackName)), UI_TIMEOUT);
|
||||
waitUntilRecyclerViewItemCount(withId(R.id.track_list), 1, UI_TIMEOUT);
|
||||
.perform(click());
|
||||
|
||||
onView(allOf(withId(R.id.track_list_item_name), withText(uniqueTrackName)))
|
||||
.perform(performLongClickOnAncestor());
|
||||
// select track
|
||||
onView(allOf(withParent(withId(R.id.track_list)), withParentIndex(0)))
|
||||
.perform(longClick());
|
||||
|
||||
clickDeleteSelectedTrackAction();
|
||||
onView(withText(android.R.string.ok))
|
||||
.inRoot(isDialog())
|
||||
.perform(performClick());
|
||||
waitUntilRecyclerViewItemCount(withId(R.id.track_list), 0, UI_TIMEOUT);
|
||||
// open menu and delete selected track
|
||||
onView(
|
||||
allOf(withContentDescription("More options"),
|
||||
childAtPosition(
|
||||
childAtPosition(
|
||||
withId(androidx.appcompat.R.id.action_mode_bar),
|
||||
1),
|
||||
2),
|
||||
isDisplayed()))
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.track_list))
|
||||
.check(matches(withRecyclerViewItemCount(0)));
|
||||
onView(withText("Delete"))
|
||||
.perform(click());
|
||||
|
||||
onView(withText("OK"))
|
||||
.perform(click());
|
||||
|
||||
// tracklist is empty now
|
||||
onView(allOf(withText("Start recording your next adventure here"), isDisplayed()));
|
||||
}
|
||||
|
||||
private void recordTrackAndOpenTrackStoppedActivity() {
|
||||
startRecordingFromTrackList(UI_TIMEOUT);
|
||||
stopRecordingToTrackStopped(UI_TIMEOUT, R.id.discard_button, R.id.finish_button);
|
||||
}
|
||||
|
||||
private void clickDeleteSelectedTrackAction() {
|
||||
try {
|
||||
waitUntilDisplayed(withId(R.id.list_context_menu_delete), UI_TIMEOUT);
|
||||
onView(withId(R.id.list_context_menu_delete))
|
||||
.perform(performClick());
|
||||
} catch (AssertionError ignored) {
|
||||
onView(allOf(
|
||||
withContentDescription(androidx.appcompat.R.string.abc_action_menu_overflow_description),
|
||||
isDescendantOfA(withId(androidx.appcompat.R.id.action_mode_bar))))
|
||||
.perform(performClick());
|
||||
onView(withText(R.string.menu_delete))
|
||||
.inRoot(isPlatformPopup())
|
||||
.perform(performClickOnAncestor());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,43 +2,34 @@ package de.dennisguse.opentracks;
|
||||
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.Espresso.openContextualActionModeOverflowMenu;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.action.ViewActions.longClick;
|
||||
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.RootMatchers.isPlatformPopup;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
||||
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 de.dennisguse.opentracks.util.EspressoUtils.performClick;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.performClickOnAncestor;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitUntilDisplayed;
|
||||
import static de.dennisguse.opentracks.util.TestEnvironmentUtils.resetTrackRecordingServiceAndDeleteTracks;
|
||||
import static de.dennisguse.opentracks.util.TrackUiTestUtils.startRecordingFromTrackList;
|
||||
import static de.dennisguse.opentracks.util.TrackUiTestUtils.stopRecordingToTrackStopped;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Locale;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoEditTrackRecordingTest {
|
||||
|
||||
private static final Duration UI_TIMEOUT = Duration.ofSeconds(10);
|
||||
|
||||
@Rule
|
||||
public final LocaleRule mLocaleRule = new LocaleRule(Locale.ENGLISH);
|
||||
|
||||
@@ -48,27 +39,27 @@ public class EspressoEditTrackRecordingTest {
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
resetTrackRecordingServiceAndDeleteTracks();
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void espressoEditTrackRecordingTest() {
|
||||
startRecordingFromTrackList(UI_TIMEOUT);
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
onView(withId(R.id.track_list_fab_action))
|
||||
.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
|
||||
// wait; stay recording
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
.perform(waitFor(4000));
|
||||
|
||||
// open menu
|
||||
onView(allOf(
|
||||
withContentDescription(androidx.appcompat.R.string.abc_action_menu_overflow_description),
|
||||
isDescendantOfA(withId(R.id.bottom_app_bar))))
|
||||
.perform(performClick());
|
||||
openContextualActionModeOverflowMenu();
|
||||
|
||||
// Click the item.
|
||||
onView(withText(R.string.menu_edit))
|
||||
.inRoot(isPlatformPopup())
|
||||
.perform(performClickOnAncestor());
|
||||
waitUntilDisplayed(withId(R.id.track_edit_name), UI_TIMEOUT);
|
||||
.perform(click());
|
||||
|
||||
// change name for "New Name"
|
||||
onView(withId(R.id.track_edit_name))
|
||||
@@ -79,11 +70,11 @@ public class EspressoEditTrackRecordingTest {
|
||||
|
||||
// save edition
|
||||
onView(withId(R.id.track_edit_save))
|
||||
.perform(performClick());
|
||||
waitUntilDisplayed(withId(R.id.track_recording_fab_action), UI_TIMEOUT);
|
||||
.perform(click());
|
||||
|
||||
// stop;
|
||||
stopRecordingToTrackStopped(UI_TIMEOUT, R.id.resume_button, R.id.finish_button);
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
.perform(longClick());
|
||||
|
||||
// it's on track stopped activity and there are two buttons
|
||||
onView(withId(R.id.resume_button))
|
||||
|
||||
@@ -1,141 +1,100 @@
|
||||
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.longClick;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withParentIndex;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.performLongClickOnAncestor;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.selectTabAtIndex;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitForResumedViewCondition;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitUntilDisplayed;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.withSelectedTab;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.withViewPagerCurrentItem;
|
||||
import static de.dennisguse.opentracks.util.TestEnvironmentUtils.resetTrackRecordingServiceAndDeleteTracks;
|
||||
import static de.dennisguse.opentracks.util.TrackUiTestUtils.finishStoppedTrack;
|
||||
import static de.dennisguse.opentracks.util.TrackUiTestUtils.resumeStoppedTrack;
|
||||
import static de.dennisguse.opentracks.util.TrackUiTestUtils.startRecordingFromTrackList;
|
||||
import static de.dennisguse.opentracks.util.TrackUiTestUtils.stopRecordingToTrackStopped;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
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.rule.GrantPermissionRule;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.RuleChain;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackBuilder;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoUITest {
|
||||
|
||||
private static final Duration UI_TIMEOUT = Duration.ofSeconds(10);
|
||||
|
||||
private final ActivityScenarioRule<TrackListActivity> activityRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
private final GrantPermissionRule grantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
@Rule
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public RuleChain rules = RuleChain.outerRule(grantPermissionRule)
|
||||
.around(activityRule);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
resetTrackRecordingServiceAndDeleteTracks();
|
||||
}
|
||||
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
@LargeTest
|
||||
@Test
|
||||
public void record_stop_resume_stop_finish() {
|
||||
startRecordingFromTrackList(UI_TIMEOUT);
|
||||
{
|
||||
stopRecordingToTrackStopped(UI_TIMEOUT, R.id.resume_button);
|
||||
resumeStoppedTrack(UI_TIMEOUT);
|
||||
stopRecordingToTrackStopped(UI_TIMEOUT, R.id.finish_button);
|
||||
finishStoppedTrack(UI_TIMEOUT);
|
||||
// TrackListActivity: start recording
|
||||
onView(withId(R.id.track_list_fab_action))
|
||||
.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity: wait to record some time and then stop
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
.perform(waitFor(5000))
|
||||
.perform(longClick());
|
||||
|
||||
onView(withId(R.id.track_list))
|
||||
.check(matches(isDisplayed()));
|
||||
// TrackStoppedActivity: resume
|
||||
onView(allOf(withId(R.id.resume_button), isClickable()))
|
||||
.perform(click());
|
||||
|
||||
// TrackRecordingActivity: wait and then stop
|
||||
onView(withId(R.id.track_recording_fab_action))
|
||||
.perform(waitFor(5000))
|
||||
.perform(longClick());
|
||||
|
||||
// TrackStoppedActivity
|
||||
onView(withId(R.id.finish_button))
|
||||
.perform(click());
|
||||
}
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void record_move_through_tabs() {
|
||||
startRecordingFromTrackList(UI_TIMEOUT);
|
||||
waitUntilDisplayed(withId(R.id.track_detail_activity_tablayout), UI_TIMEOUT);
|
||||
waitUntilDisplayed(withId(R.id.track_detail_activity_view_pager), UI_TIMEOUT);
|
||||
{
|
||||
// TrackListActivity: start recording
|
||||
onView(withId(R.id.track_list_fab_action))
|
||||
.perform(click());
|
||||
}
|
||||
{
|
||||
// TrackRecordingActivity
|
||||
ViewInteraction tabLayout = onView(withId(R.id.track_detail_activity_tablayout));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(1));
|
||||
waitUntilTabSelectionSettlesAt(1);
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(2));
|
||||
waitUntilTabSelectionSettlesAt(2);
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(3));
|
||||
waitUntilTabSelectionSettlesAt(3);
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
tabLayout.perform(selectTabAtIndex(0));
|
||||
waitUntilTabSelectionSettlesAt(0);
|
||||
tabLayout.perform(waitFor(1000));
|
||||
|
||||
// stop
|
||||
stopRecordingToTrackStopped(UI_TIMEOUT, R.id.resume_button, R.id.finish_button);
|
||||
|
||||
onView(withId(R.id.resume_button))
|
||||
.check(matches(isDisplayed()));
|
||||
onView(withId(R.id.finish_button))
|
||||
.check(matches(isDisplayed()));
|
||||
onView(withId(R.id.track_recording_fab_action)).
|
||||
perform(longClick());
|
||||
}
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void selectAndDeleteTrack() {
|
||||
Pair<Track, List<TrackPoint>> pair = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 5);
|
||||
String uniqueTrackName = "SelectAndDeleteTrack-" + System.currentTimeMillis();
|
||||
TrackBuilder trackBuilder = new TrackBuilder(pair.first);
|
||||
trackBuilder.setName(uniqueTrackName);
|
||||
TestDataUtil.insertTrackWithLocations(new ContentProviderUtils(ApplicationProvider.getApplicationContext()), trackBuilder.getTrack(), pair.second);
|
||||
activityRule.getScenario().recreate();
|
||||
waitUntilDisplayed(allOf(withId(R.id.track_list_item_name), withText(uniqueTrackName)), UI_TIMEOUT);
|
||||
|
||||
onView(withId(R.id.track_list)).check(matches(isDisplayed()));
|
||||
onView(allOf(withId(R.id.track_list_item_name), withText(uniqueTrackName)))
|
||||
.perform(performLongClickOnAncestor());
|
||||
|
||||
onView(withId(androidx.appcompat.R.id.action_mode_bar))
|
||||
.check(matches(isDisplayed()));
|
||||
}
|
||||
|
||||
private void waitUntilTabSelectionSettlesAt(int index) {
|
||||
waitForResumedViewCondition(
|
||||
withId(R.id.track_detail_activity_tablayout),
|
||||
view -> withSelectedTab(index).matches(view),
|
||||
"selected tab to be " + index,
|
||||
UI_TIMEOUT);
|
||||
waitForResumedViewCondition(
|
||||
withId(R.id.track_detail_activity_view_pager),
|
||||
view -> withViewPagerCurrentItem(index).matches(view),
|
||||
"ViewPager current item to be " + index,
|
||||
UI_TIMEOUT);
|
||||
onView(allOf(withParent(withId(R.id.track_list)), withParentIndex(0))).perform(longClick());
|
||||
}
|
||||
}
|
||||
|
||||
+49
-65
@@ -20,19 +20,22 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -75,6 +78,7 @@ import de.dennisguse.opentracks.util.FileUtils;
|
||||
* @author Bartlomiej Niechwiej
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CustomContentProviderUtilsTest {
|
||||
|
||||
private static final String NAME_PREFIX = "test name";
|
||||
@@ -86,6 +90,12 @@ public class CustomContentProviderUtilsTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@Mock
|
||||
private Cursor cursorMock;
|
||||
|
||||
@Mock
|
||||
private ContentResolver contentResolverMock;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
@@ -94,21 +104,21 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noPoints() {
|
||||
assertEquals(0, testIterator(new Track.Id(1), 0).size());
|
||||
testIterator(new Track.Id(1), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_noAscending() {
|
||||
assertEquals(50, testIterator(new Track.Id(1), 50).size());
|
||||
assertEquals(50, testIterator(new Track.Id(2), 50).size());
|
||||
testIterator(new Track.Id(1), 50);
|
||||
testIterator(new Track.Id(2), 50);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationIterator_largeTrack() {
|
||||
assertEquals(20000 / 2, testIterator(new Track.Id(1), 20000 / 2).size());
|
||||
testIterator(new Track.Id(1), 20000 / 2);
|
||||
}
|
||||
|
||||
private List<TrackPoint> testIterator(Track.Id trackId, int numPoints) {
|
||||
private void testIterator(Track.Id trackId, int numPoints) {
|
||||
TrackPoint.Id lastPointId = initializeTrack(trackId, numPoints);
|
||||
List<TrackPoint> locations = new ArrayList<>(numPoints);
|
||||
try (TrackPointIterator it = contentProviderUtils.getTrackPointIterator(trackId, null)) {
|
||||
@@ -121,7 +131,6 @@ public class CustomContentProviderUtilsTest {
|
||||
}
|
||||
assertEquals(numPoints, locations.size());
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
||||
private TrackPoint.Id initializeTrack(Track.Id id, int numPoints) {
|
||||
@@ -178,62 +187,40 @@ public class CustomContentProviderUtilsTest {
|
||||
@Test
|
||||
public void testCreateTrack() {
|
||||
Track.Id trackId = new Track.Id(System.currentTimeMillis());
|
||||
|
||||
int columnIndex = 1;
|
||||
// Id
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex);
|
||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||
when(cursorMock.getLong(columnIndex)).thenReturn(trackId.id());
|
||||
|
||||
//Uuid
|
||||
columnIndex++;
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.UUID)).thenReturn(columnIndex);
|
||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||
when(cursorMock.getBlob(columnIndex)).thenReturn(UUIDUtils.toBytes(UUID.randomUUID()));
|
||||
|
||||
// Name
|
||||
columnIndex++;
|
||||
String name = NAME_PREFIX + trackId.id();
|
||||
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex);
|
||||
when(cursorMock.isNull(columnIndex)).thenReturn(false);
|
||||
when(cursorMock.getString(columnIndex)).thenReturn(name);
|
||||
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{
|
||||
TracksColumns._ID,
|
||||
TracksColumns.UUID,
|
||||
TracksColumns.NAME,
|
||||
TracksColumns.DESCRIPTION,
|
||||
TracksColumns.ACTIVITY_TYPE,
|
||||
TracksColumns.ACTIVITY_TYPE_LOCALIZED,
|
||||
TracksColumns.STARTTIME,
|
||||
TracksColumns.STARTTIME_OFFSET,
|
||||
TracksColumns.STOPTIME,
|
||||
TracksColumns.TOTALDISTANCE,
|
||||
TracksColumns.TOTALTIME,
|
||||
TracksColumns.MOVINGTIME,
|
||||
TracksColumns.MAXSPEED,
|
||||
TracksColumns.MIN_ALTITUDE,
|
||||
TracksColumns.MAX_ALTITUDE,
|
||||
TracksColumns.ALTITUDE_GAIN,
|
||||
TracksColumns.ALTITUDE_LOSS,
|
||||
});
|
||||
cursor.addRow(new Object[]{
|
||||
trackId.id(),
|
||||
UUIDUtils.toBytes(UUID.randomUUID()),
|
||||
name,
|
||||
"",
|
||||
ActivityType.UNKNOWN.getId(),
|
||||
"",
|
||||
1_000L,
|
||||
ZoneOffset.UTC.getTotalSeconds(),
|
||||
2_000L,
|
||||
123.45f,
|
||||
1_000L,
|
||||
500L,
|
||||
12.34f,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
});
|
||||
assertTrue(cursor.moveToFirst());
|
||||
|
||||
Track track = ContentProviderUtils.createTrack(cursor);
|
||||
Track track = ContentProviderUtils.createTrack(cursorMock);
|
||||
assertEquals(trackId, track.id());
|
||||
assertEquals(name, track.name());
|
||||
}
|
||||
|
||||
private void assertCount(int trackCount, int trackPointCount, int markerCount) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
try (Cursor tracksCursor = contentResolver.query(CustomContentProvider.UrlType.TRACKS.getUri(), null, null, null, TracksColumns._ID)) {
|
||||
try (Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID)) {
|
||||
assertEquals(trackCount, tracksCursor.getCount());
|
||||
}
|
||||
try (Cursor tracksPointsCursor = contentResolver.query(CustomContentProvider.UrlType.TRACKPOINTS_BY_ID.getUri(), null, null, null, TrackPointsColumns._ID)) {
|
||||
try (Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID)) {
|
||||
assertEquals(trackPointCount, tracksPointsCursor.getCount());
|
||||
}
|
||||
try (Cursor markerCursor = contentResolver.query(CustomContentProvider.UrlType.MARKERS.getUri(), null, null, null, MarkerColumns._ID)) {
|
||||
try (Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID)) {
|
||||
assertEquals(markerCount, markerCursor.getCount());
|
||||
}
|
||||
}
|
||||
@@ -462,6 +449,8 @@ public class CustomContentProviderUtilsTest {
|
||||
marker.setDescription(TEST_DESC);
|
||||
contentProviderUtils.insertMarker(marker.getMarker());
|
||||
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
|
||||
|
||||
Marker.Id markerId = new Marker.Id(System.currentTimeMillis());
|
||||
marker.setId(markerId);
|
||||
ContentValues contentValues = contentProviderUtils.createContentValues(marker.getMarker(), trackId);
|
||||
@@ -1195,7 +1184,7 @@ public class CustomContentProviderUtilsTest {
|
||||
assertEquals(sensorStatistics.avgPower().getW(), stats.avgPower, 0f);
|
||||
}
|
||||
|
||||
private Pair<SensorStatistics, TestSensorDataUtil.SensorDataStats> testGetSensorStats_randomData(int totalPoints, boolean withStartSegments) {
|
||||
private void testGetSensorStats_randomData(int totalPoints, boolean withStartSegments) {
|
||||
// given
|
||||
Instant start = Instant.now();
|
||||
TestSensorDataUtil sensorDataUtil = new TestSensorDataUtil();
|
||||
@@ -1218,27 +1207,22 @@ public class CustomContentProviderUtilsTest {
|
||||
SensorStatistics sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
TestSensorDataUtil.SensorDataStats stats = sensorDataUtil.computeStats();
|
||||
|
||||
return Pair.create(sensorStatistics, stats);
|
||||
// then
|
||||
assertEquals(sensorStatistics.avgHeartRate().getBPM(), stats.avgHr, 0.01f);
|
||||
assertEquals(sensorStatistics.maxHeartRate().getBPM(), stats.maxHr, 0.01f);
|
||||
assertEquals(sensorStatistics.avgCadence().getRPM(), stats.avgCadence, 0.01f);
|
||||
assertEquals(sensorStatistics.maxCadence().getRPM(), stats.maxCadence, 0.01f);
|
||||
assertEquals(sensorStatistics.avgPower().getW(), stats.avgPower, 0.01f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_veryLongActivity12h() {
|
||||
Pair<SensorStatistics, TestSensorDataUtil.SensorDataStats> result = testGetSensorStats_randomData(43200 / 6, false);
|
||||
assertEquals(result.first.avgHeartRate().getBPM(), result.second.avgHr, 0.01f);
|
||||
assertEquals(result.first.maxHeartRate().getBPM(), result.second.maxHr, 0.01f);
|
||||
assertEquals(result.first.avgCadence().getRPM(), result.second.avgCadence, 0.01f);
|
||||
assertEquals(result.first.maxCadence().getRPM(), result.second.maxCadence, 0.01f);
|
||||
assertEquals(result.first.avgPower().getW(), result.second.avgPower, 0.01f);
|
||||
testGetSensorStats_randomData(43200 / 6, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSensorStats_withSeveralRandomStartSegments() {
|
||||
Pair<SensorStatistics, TestSensorDataUtil.SensorDataStats> result = testGetSensorStats_randomData(5000, true);
|
||||
assertEquals(result.first.avgHeartRate().getBPM(), result.second.avgHr, 0.01f);
|
||||
assertEquals(result.first.maxHeartRate().getBPM(), result.second.maxHr, 0.01f);
|
||||
assertEquals(result.first.avgCadence().getRPM(), result.second.avgCadence, 0.01f);
|
||||
assertEquals(result.first.maxCadence().getRPM(), result.second.maxCadence, 0.01f);
|
||||
assertEquals(result.first.avgPower().getW(), result.second.avgPower, 0.01f);
|
||||
testGetSensorStats_randomData(5000, true);
|
||||
}
|
||||
|
||||
private TrackPoint getLastValidTrackPoint(Track.Id trackId) {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ActivityTypeTest {
|
||||
|
||||
@Test
|
||||
public void findBy_id() {
|
||||
assertEquals(ActivityType.TRAIL_RUNNING, ActivityType.findBy("trail running"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBy_idOfEveryActivityType() {
|
||||
for (ActivityType activityType : ActivityType.values()) {
|
||||
assertEquals(activityType, ActivityType.findBy(activityType.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBy_synonymsOfEveryActivityType() {
|
||||
for (ActivityType activityType : ActivityType.values()) {
|
||||
for (String synonym : activityType.synonyms) {
|
||||
assertEquals(activityType, ActivityType.findBy(synonym));
|
||||
assertEquals(activityType, ActivityType.findBy(synonym.toUpperCase(Locale.ROOT).replace(' ', '_')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBy_ignoresCase() {
|
||||
assertEquals(ActivityType.RUNNING, ActivityType.findBy("RUNNING"));
|
||||
assertEquals(ActivityType.ATV, ActivityType.findBy("atv"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBy_ignoresSeparators() {
|
||||
assertEquals(ActivityType.TRAIL_RUNNING, ActivityType.findBy("TRAIL_RUNNING"));
|
||||
assertEquals(ActivityType.MOUNTAIN_BIKING, ActivityType.findBy("mountain-biking"));
|
||||
assertEquals(ActivityType.CROSS_COUNTRY_SKIING, ActivityType.findBy("cross_country_skiing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBy_ignoresSurroundingAndRepeatedWhitespace() {
|
||||
assertEquals(ActivityType.MOUNTAIN_BIKING, ActivityType.findBy(" Mountain Biking "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBy_synonym() {
|
||||
assertEquals(ActivityType.RUNNING, ActivityType.findBy("treadmill"));
|
||||
assertEquals(ActivityType.TRAIL_RUNNING, ActivityType.findBy("TRAIL_RUN"));
|
||||
assertEquals(ActivityType.MOUNTAIN_BIKING, ActivityType.findBy("MOUNTAIN_BIKE"));
|
||||
assertEquals(ActivityType.SWIMMING_OPEN, ActivityType.findBy("SWIMMING_OPENWATER"));
|
||||
assertEquals(ActivityType.BIKING, ActivityType.findBy("bike"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBy_unknown() {
|
||||
assertEquals(ActivityType.UNKNOWN, ActivityType.findBy("hovercraft"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBy_null() {
|
||||
assertEquals(ActivityType.UNKNOWN, ActivityType.findBy(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findBy_blank() {
|
||||
assertEquals(ActivityType.UNKNOWN, ActivityType.findBy(" "));
|
||||
}
|
||||
}
|
||||
+27
-51
@@ -66,8 +66,7 @@ import de.dennisguse.opentracks.data.models.TrackBuilder;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingPowerBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Aggregator;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
||||
@@ -75,9 +74,10 @@ import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpee
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorTemperature;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataAggregator;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Export a track to {@link TrackFileFormat} and verify that the import is identical.
|
||||
@@ -94,20 +94,18 @@ public class ExportImportTest {
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
|
||||
//For csv_export_only() as the timezone is hardcoded in the expectation.
|
||||
//For csv_export_only() as we the timezone is hardcoded in the expectation.
|
||||
@Rule
|
||||
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
|
||||
|
||||
private static final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
|
||||
PreferencesUtils.resetPreferences(context, true);
|
||||
}
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
|
||||
private static final ActivityType TRACK_ACTIVITY_TYPE = ActivityType.MOUNTAIN_BIKING;
|
||||
@@ -164,7 +162,7 @@ public class ExportImportTest {
|
||||
"Marker 1 desc",
|
||||
"Marker 1 typeLocalized",
|
||||
new Position(
|
||||
Instant.parse("2020-02-02T02:02:03Z"),
|
||||
Instant.parse("2020-02-02T02:02:03Z"),
|
||||
3.1234567, 14.0014567,
|
||||
Distance.of(10),
|
||||
Altitude.WGS84.of(1020.25),
|
||||
@@ -199,6 +197,7 @@ public class ExportImportTest {
|
||||
));
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
||||
trackPointCreator.getSensorManager().sensorDataAggregator = new SensorDataAggregator();
|
||||
service.endCurrentTrack();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
||||
@@ -213,6 +212,7 @@ public class ExportImportTest {
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3.1234567, 16.001, 10, 27, 15, 999.123, 0f);
|
||||
|
||||
trackPointCreator.getSensorManager().sensorDataAggregator = new SensorDataAggregator();
|
||||
trackPointCreator.setClock("2020-02-02T02:04:00Z");
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -296,22 +296,7 @@ public class ExportImportTest {
|
||||
Power.of(50),
|
||||
new AltitudeGainLoss(0, 0)
|
||||
),
|
||||
new TrackPoint(
|
||||
null,
|
||||
TrackPoint.Type.SEGMENT_END_MANUAL,
|
||||
new Position(
|
||||
Instant.parse("2020-02-02T02:02:18Z"),
|
||||
3.123456, 14.001456, Distance.of(10),
|
||||
Altitude.WGS84.of(1020.25), null,
|
||||
null,
|
||||
Speed.of(5)),
|
||||
Distance.of(0),
|
||||
HeartRate.of(69),
|
||||
null,
|
||||
Cadence.of(3),
|
||||
Power.of(50),
|
||||
new AltitudeGainLoss(0, 0)
|
||||
),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:02:18Z")),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse("2020-02-02T02:03:20Z")),
|
||||
new TrackPoint(
|
||||
null,
|
||||
@@ -369,16 +354,7 @@ public class ExportImportTest {
|
||||
null,
|
||||
null,
|
||||
new AltitudeGainLoss(0, 0)),
|
||||
new TrackPoint(
|
||||
null,
|
||||
TrackPoint.Type.SEGMENT_END_MANUAL,
|
||||
Position.of(Instant.parse("2020-02-02T02:04:00Z")),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new AltitudeGainLoss(0, 0))
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse("2020-02-02T02:04:00Z"))
|
||||
), actual);
|
||||
|
||||
assertEquals(new Statistics(
|
||||
@@ -684,17 +660,17 @@ public class ExportImportTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain, float temperature) {
|
||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||
private void mockSensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power, Float altitudeGain, float temperature) {
|
||||
SensorDataAggregator sensorDataAggregator = trackPointCreator.getSensorManager().sensorDataAggregator;
|
||||
|
||||
AggregatorCyclingPower cyclingPower = new AggregatorCyclingPower("", "");
|
||||
cyclingPower.add(trackPointCreator.getNow(), new CyclingPowerBluetooth.Data(Power.of(power), null));
|
||||
sensorManager.setAggregator(cyclingPower);
|
||||
cyclingPower.add(new Raw<>(trackPointCreator.createNow(), new BluetoothHandlerCyclingPower.Data(Power.of(power), null)));
|
||||
sensorDataAggregator.add(cyclingPower);
|
||||
|
||||
|
||||
AggregatorHeartRate avgHeartRate = new AggregatorHeartRate("", "");
|
||||
avgHeartRate.add(trackPointCreator.getNow(), HeartRate.of(heartRate));
|
||||
sensorManager.setAggregator(avgHeartRate);
|
||||
avgHeartRate.add(new Raw<>(trackPointCreator.createNow(), HeartRate.of(heartRate)));
|
||||
sensorDataAggregator.add(avgHeartRate);
|
||||
|
||||
AggregatorCyclingCadence cyclingCadence = new AggregatorCyclingCadence("", "") {
|
||||
@NonNull
|
||||
@@ -708,7 +684,7 @@ public class ExportImportTest {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
sensorManager.setAggregator(cyclingCadence);
|
||||
sensorDataAggregator.add(cyclingCadence);
|
||||
|
||||
if (distance != null && speed != null) {
|
||||
AggregatorCyclingDistanceSpeed aggregatorCyclingDistanceSpeed = new AggregatorCyclingDistanceSpeed("", "") {
|
||||
@@ -723,14 +699,14 @@ public class ExportImportTest {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
sensorManager.setAggregator(aggregatorCyclingDistanceSpeed);
|
||||
sensorDataAggregator.add(aggregatorCyclingDistanceSpeed);
|
||||
} else {
|
||||
sensorManager.setAggregator(new AggregatorCyclingDistanceSpeed("", ""));
|
||||
sensorDataAggregator.add(new AggregatorCyclingDistanceSpeed("", ""));
|
||||
}
|
||||
|
||||
mockAltitudeChange(trackPointCreator, altitudeGain);
|
||||
|
||||
sensorManager.setAggregator(new AggregatorTemperature("", "'") {
|
||||
sensorDataAggregator.add(new AggregatorTemperature("", "'") {
|
||||
@NonNull
|
||||
@Override
|
||||
public Temperature getAggregatedValue(Instant now) {
|
||||
@@ -746,11 +722,11 @@ public class ExportImportTest {
|
||||
trackPointCreator.onChange();
|
||||
}
|
||||
|
||||
private static void mockAltitudeChange(TrackPointCreator trackPointCreator, Float altitudeGain) {
|
||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, Float altitudeGain) {
|
||||
SensorDataAggregator sensorDataAggregator = trackPointCreator.getSensorManager().sensorDataAggregator;
|
||||
|
||||
if (altitudeGain == null) {
|
||||
sensorManager.getAltitudeChangeHandler().setAggregator(new AggregatorBarometer("test", null));
|
||||
sensorDataAggregator.add(new AggregatorBarometer("test", null));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -766,10 +742,10 @@ public class ExportImportTest {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
sensorManager.getAltitudeChangeHandler().setAggregator(aggregatorBarometer);
|
||||
sensorDataAggregator.add(aggregatorBarometer);
|
||||
}
|
||||
|
||||
public static void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
|
||||
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
|
||||
Location location = new Location("mock");
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
@@ -781,6 +757,6 @@ public class ExportImportTest {
|
||||
mockAltitudeChange(trackPointCreator, altitudeGain);
|
||||
|
||||
trackPointCreator.setClock(time);
|
||||
trackPointCreator.getSensorManager().getGpsHandler().onDataReceived(location);
|
||||
trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location);
|
||||
}
|
||||
}
|
||||
+2
-180
@@ -120,184 +120,6 @@ public class GPXTrackImporterTest {
|
||||
), importedTrackPoints);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx_missing_time() throws IOException {
|
||||
// given
|
||||
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_missing_time);
|
||||
|
||||
// when
|
||||
// 1. import
|
||||
importTrackId = importer.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 2. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("", importedTrack.activityTypeLocalized());
|
||||
assertEquals("", importedTrack.description());
|
||||
assertEquals("20210907_213924.gpx", importedTrack.name());
|
||||
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
|
||||
|
||||
// 3. trackstatistics
|
||||
Statistics trackStatistics = importedTrack.statistics();
|
||||
assertEquals(0, trackStatistics.maxSpeed().toMPS(), 0.01);
|
||||
assertEquals(Duration.ofSeconds(0), trackStatistics.movingDuration());
|
||||
|
||||
// 4. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
assertEquals(1, importedTrackPoints.size());
|
||||
|
||||
TrackPointAssert a = new TrackPointAssert();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
|
||||
new Position(
|
||||
Instant.parse("2021-09-07T22:12:00Z"),
|
||||
30.14185982, -40.3863038, null,
|
||||
Altitude.WGS84.of(-5), null,
|
||||
null,
|
||||
null))
|
||||
), importedTrackPoints);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx_with_power_pwd() throws IOException {
|
||||
// given
|
||||
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_with_power_pwr);
|
||||
|
||||
// when
|
||||
// 1. import
|
||||
importTrackId = importer.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 2. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("", importedTrack.activityTypeLocalized());
|
||||
assertEquals("", importedTrack.description());
|
||||
assertEquals("gpx with power", importedTrack.name());
|
||||
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
assertEquals(3, importedTrackPoints.size());
|
||||
|
||||
TrackPointAssert a = new TrackPointAssert();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(null, TrackPoint.Type.SEGMENT_START_AUTOMATIC,
|
||||
new Position(
|
||||
Instant.parse("2021-09-07T22:10:19Z"),
|
||||
30.14185982, -40.3863038, null,
|
||||
Altitude.WGS84.of(-5), null,
|
||||
null,
|
||||
null),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new Power(100f),
|
||||
null
|
||||
),
|
||||
new TrackPoint(null, TrackPoint.Type.TRACKPOINT,
|
||||
new Position(
|
||||
Instant.parse("2021-09-07T22:11:07Z"),
|
||||
30.14184657, -40.38670089, null,
|
||||
Altitude.WGS84.of(-5), null,
|
||||
null,
|
||||
Speed.of(0.7976524233818054)),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new Power(110f),
|
||||
null),
|
||||
new TrackPoint(null, TrackPoint.Type.TRACKPOINT,
|
||||
new Position(
|
||||
Instant.parse("2021-09-07T22:12:00Z"),
|
||||
30.14185982, -40.3863038, null,
|
||||
Altitude.WGS84.of(-5), null,
|
||||
null,
|
||||
Speed.of(0.7224021553993225)),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new Power(150f),
|
||||
null)
|
||||
), importedTrackPoints);
|
||||
}
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx_with_power_gpxpx() throws IOException {
|
||||
// given
|
||||
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_with_power_gpxpx);
|
||||
|
||||
// when
|
||||
// 1. import
|
||||
importTrackId = importer.importFile(inputStream).get(0);
|
||||
|
||||
// then
|
||||
// 2. track
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
assertNotNull(importedTrack);
|
||||
assertEquals("", importedTrack.activityTypeLocalized());
|
||||
assertEquals("", importedTrack.description());
|
||||
assertEquals("gpx with power", importedTrack.name());
|
||||
assertEquals(ActivityType.UNKNOWN, importedTrack.activityType());
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
assertEquals(3, importedTrackPoints.size());
|
||||
|
||||
TrackPointAssert a = new TrackPointAssert();
|
||||
a.assertEquals(List.of(
|
||||
new TrackPoint(null, TrackPoint.Type.SEGMENT_START_AUTOMATIC,
|
||||
new Position(
|
||||
Instant.parse("2021-09-07T22:10:19Z"),
|
||||
30.14185982, -40.3863038, null,
|
||||
Altitude.WGS84.of(-5), null,
|
||||
null,
|
||||
null),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new Power(100f),
|
||||
null
|
||||
),
|
||||
new TrackPoint(null, TrackPoint.Type.TRACKPOINT,
|
||||
new Position(
|
||||
Instant.parse("2021-09-07T22:11:07Z"),
|
||||
30.14184657, -40.38670089, null,
|
||||
Altitude.WGS84.of(-5), null,
|
||||
null,
|
||||
Speed.of(0.7976524233818054)),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new Power(110f),
|
||||
null),
|
||||
new TrackPoint(null, TrackPoint.Type.TRACKPOINT,
|
||||
new Position(
|
||||
Instant.parse("2021-09-07T22:12:00Z"),
|
||||
30.14185982, -40.3863038, null,
|
||||
Altitude.WGS84.of(-5), null,
|
||||
null,
|
||||
Speed.of(0.7224021553993225)),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new Power(150f),
|
||||
null)
|
||||
), importedTrackPoints);
|
||||
}
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
public void gpx_speed_no_namespace() throws IOException {
|
||||
@@ -463,8 +285,8 @@ public class GPXTrackImporterTest {
|
||||
public void importExportTest_timezone() throws IOException {
|
||||
// given
|
||||
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_timezone);
|
||||
try (InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_timezone)) {
|
||||
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
|
||||
try (InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone)) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
// when
|
||||
|
||||
@@ -26,7 +26,6 @@ public class TrackPointAssert {
|
||||
|
||||
Assert.assertEquals("has altitude", expected.position().hasAltitude(), actual.position().hasAltitude());
|
||||
if (expected.position().hasAltitude()) {
|
||||
Assert.assertEquals("altitude", expected.position().altitude().getClass(), actual.position().altitude().getClass());
|
||||
Assert.assertEquals("altitude", expected.position().altitude().toM(), actual.position().altitude().toM(), delta);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DataProviderTest {
|
||||
@@ -24,7 +24,7 @@ public class DataProviderTest {
|
||||
@Test
|
||||
public void supportsVersion2_tracks() {
|
||||
// given
|
||||
Uri uri = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||
String[] expectedTracksColumns = new String[]{
|
||||
"_id",
|
||||
"name",
|
||||
@@ -64,7 +64,7 @@ public class DataProviderTest {
|
||||
@Test
|
||||
public void supportsVersion2_trackpoints() {
|
||||
// given
|
||||
Uri uri = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||
String[] expectedTrackpointsColumns = {
|
||||
"_id",
|
||||
"trackid",
|
||||
@@ -94,7 +94,7 @@ public class DataProviderTest {
|
||||
@Test
|
||||
public void supportsVersion2_markers() {
|
||||
// given
|
||||
Uri uri = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||
String[] expectedMarkersColumns = {
|
||||
"_id",
|
||||
"name",
|
||||
@@ -126,7 +126,7 @@ public class DataProviderTest {
|
||||
@Test
|
||||
public void supportsVersion3_tracks() {
|
||||
// given
|
||||
Uri uri = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/tracks/1");
|
||||
String[] expectedTracksColumns = {
|
||||
"_id",
|
||||
"name",
|
||||
@@ -164,7 +164,7 @@ public class DataProviderTest {
|
||||
@Test
|
||||
public void supportsVersion3_trackpoints() {
|
||||
//given
|
||||
Uri uri = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/trackpoints/1");
|
||||
String[] expectedTrackpointsColumns = {
|
||||
"_id",
|
||||
"trackid",
|
||||
@@ -194,7 +194,7 @@ public class DataProviderTest {
|
||||
@Test
|
||||
public void supportsVersion3_markers() {
|
||||
// given
|
||||
Uri uri = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||
Uri uri = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/dashboard/markers/1");
|
||||
String[] expectedMarkersColumns = {
|
||||
"_id",
|
||||
"name",
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -13,11 +13,11 @@ public class BluetoothHandlerBarometricPressureTest {
|
||||
@Test
|
||||
public void parseEnvironmentalSensing_Pa() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BarometerBluetooth.BAROMETRIC_PRESSURE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerBarometricPressure.BAROMETRIC_PRESSURE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{(byte) 0xB2, (byte) 0x48, (byte) 0x0F, (byte) 0x00});
|
||||
|
||||
// when
|
||||
AtmosphericPressure pressure = BarometerBluetooth.parseEnvironmentalSensing(characteristic);
|
||||
AtmosphericPressure pressure = BluetoothHandlerBarometricPressure.parseEnvironmentalSensing(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(AtmosphericPressure.ofHPA(1001.65f), pressure);
|
||||
+8
-8
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -8,14 +8,14 @@ import android.util.Pair;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class CyclingDistanceSpeedBluetoothTest {
|
||||
public class BluetoothHandlerCyclingDistanceSpeedTest {
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
||||
|
||||
// when
|
||||
Pair<CyclingDistanceSpeedBluetooth.WheelData, CyclingCadenceBluetooth.CrankData> sensor = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
assertNull(sensor.first);
|
||||
@@ -24,11 +24,11 @@ public class CyclingDistanceSpeedBluetoothTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_wheelOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
Pair<CyclingDistanceSpeedBluetooth.WheelData, CyclingCadenceBluetooth.CrankData> sensor = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(65535 + 16777216, sensor.first.wheelRevolutionsCount());
|
||||
@@ -37,11 +37,11 @@ public class CyclingDistanceSpeedBluetoothTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankWheel() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingDistanceSpeedBluetooth.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingDistanceSpeed.CYCLING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
Pair<CyclingDistanceSpeedBluetooth.WheelData, CyclingCadenceBluetooth.CrankData> sensor = CyclingDistanceSpeedBluetooth.parseCyclingCrankAndWheel(characteristic);
|
||||
Pair<BluetoothHandlerCyclingDistanceSpeed.WheelData, BluetoothHandlerCyclingCadence.CrankData> sensor = BluetoothHandlerCyclingDistanceSpeed.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(200 + 16777216, sensor.first.wheelRevolutionsCount());
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class BluetoothHandlerCyclingPowerTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingPower_power() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingPower.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0, 0, 40, 0});
|
||||
|
||||
// when
|
||||
BluetoothHandlerCyclingPower.Data powerCadence = BluetoothHandlerCyclingPower.parseCyclingPower(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(40, powerCadence.power().getW(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCyclingPower_power_with_cadence() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerCyclingPower.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42});
|
||||
|
||||
// when
|
||||
BluetoothHandlerCyclingPower.Data powerCadence = BluetoothHandlerCyclingPower.parseCyclingPower(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(0, powerCadence.power().getW(), 0.01);
|
||||
|
||||
assertEquals(12, powerCadence.crank().crankRevolutionsCount());
|
||||
assertEquals(17125, powerCadence.crank().crankRevolutionsTime());
|
||||
}
|
||||
|
||||
}
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -13,11 +13,11 @@ public class BluetoothHandlerHeartRateTest {
|
||||
@Test
|
||||
public void parseHeartRate_uint8() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(HeartRateBluetooth.HEARTRATE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerHeartRate.HEARTRATE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||
|
||||
// when
|
||||
HeartRate heartRate = HeartRateBluetooth.parseHeartRate(characteristic);
|
||||
HeartRate heartRate = BluetoothHandlerHeartRate.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(HeartRate.of(60), heartRate);
|
||||
@@ -26,11 +26,11 @@ public class BluetoothHandlerHeartRateTest {
|
||||
@Test
|
||||
public void parseHeartRate_uint16() {
|
||||
// given
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(HeartRateBluetooth.HEARTRATE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerHeartRate.HEARTRATE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||
|
||||
// when
|
||||
HeartRate heartRate = HeartRateBluetooth.parseHeartRate(characteristic);
|
||||
HeartRate heartRate = BluetoothHandlerHeartRate.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(HeartRate.of(257), heartRate);
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -10,15 +10,15 @@ import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
|
||||
public class RunningSpeedAndCadenceBluetoothTest {
|
||||
public class BluetoothHandlerRunningSpeedAndCadenceTest {
|
||||
|
||||
@Test
|
||||
public void parseRunningSpeedAndCadence_with_distance() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(RunningSpeedAndCadenceBluetooth.RUNNING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothHandlerRunningSpeedAndCadence.RUNNING_SPEED_CADENCE.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{2, 0, 5, 80, (byte) 0xFF, (byte) 0xFF, 0, 1});
|
||||
|
||||
// when
|
||||
RunningSpeedAndCadenceBluetooth.Data sensor = RunningSpeedAndCadenceBluetooth.parseRunningSpeedAndCadence("sensorName", characteristic);
|
||||
BluetoothHandlerRunningSpeedAndCadence.Data sensor = BluetoothHandlerRunningSpeedAndCadence.parseRunningSpeedAndCadence("sensorName", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(Speed.of(5), sensor.speed());
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class BoschEbikeParserTest {
|
||||
|
||||
@Test
|
||||
public void parse_multipleFrames_returnsCombinedMeasurements() {
|
||||
byte[] raw = new byte[] {
|
||||
// Bosch cadence frame
|
||||
0x30, 0x04, (byte) 0x98, 0x5A, 0x08, 0x76,
|
||||
// Bosch human power frame
|
||||
0x30, 0x05, (byte) 0x98, 0x5B, 0x08, (byte) 0x83, 0x01,
|
||||
// Bosch speed frame
|
||||
0x30, 0x07, (byte) 0x98, 0x2D, 0x08, (byte) 0x94, 0x0B, 0x10, 0x01
|
||||
};
|
||||
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(raw);
|
||||
|
||||
assertNotNull(data);
|
||||
assertEquals(59.0f, data.cadence().getRPM(), 0.01f);
|
||||
assertEquals(131.0f, data.humanPower().getW(), 0.01f);
|
||||
assertEquals(14.28 / 3.6, data.speed().toMPS(), 0.0001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_multiByteVarint_returnsScaledSpeed() {
|
||||
// Bosch speed frame using a multi-byte varint
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(new byte[] {
|
||||
0x30, 0x07, (byte) 0x98, 0x2D, 0x08, (byte) 0xE1, 0x0E, 0x10, 0x01
|
||||
});
|
||||
|
||||
assertNotNull(data);
|
||||
assertEquals(18.89 / 3.6, data.speed().toMPS(), 0.0001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_zeroValueFrame_returnsZeroCadence() {
|
||||
// Bosch zero-value cadence frame
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(new byte[] {
|
||||
0x30, 0x02, (byte) 0x98, 0x5A
|
||||
});
|
||||
|
||||
assertNotNull(data);
|
||||
assertEquals(0.0f, data.cadence().getRPM(), 0.01f);
|
||||
assertNull(data.speed());
|
||||
assertNull(data.humanPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_unsupportedMeasurementsOnly_returnsNull() {
|
||||
// Bosch frame for message 0x9865, which the parser does not map to a measurement.
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(new byte[] {
|
||||
0x30, 0x02, (byte) 0x98, 0x65
|
||||
});
|
||||
|
||||
assertNull(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_unimplementedMeasurementsOnly_returnsNull() {
|
||||
// Bosch motor power frame: message 0x985D with value 0x42, currently ignored.
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(new byte[] {
|
||||
0x30, 0x04, (byte) 0x98, 0x5D, 0x08, 0x42
|
||||
});
|
||||
|
||||
assertNull(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_malformedVarint_returnsNull() {
|
||||
// Truncated Bosch speed frame: starts like a real 0x982D frame but the varint is incomplete.
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(new byte[] {
|
||||
0x30, 0x04, (byte) 0x98, 0x2D, 0x08, (byte) 0x94
|
||||
});
|
||||
|
||||
assertNull(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_varintLargerThan32Bit_returnsNull() {
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(new byte[] {
|
||||
0x30, 0x08, (byte) 0x98, 0x2D, 0x08,
|
||||
(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01
|
||||
});
|
||||
|
||||
assertNull(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_validMeasurementBeforeMalformedFrame_returnsValidMeasurement() {
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(new byte[] {
|
||||
// Bosch cadence frame: message 0x985A with value 0x76
|
||||
0x30, 0x04, (byte) 0x98, 0x5A, 0x08, 0x76,
|
||||
// Truncated Bosch speed frame: malformed trailing frame should not discard the valid cadence above.
|
||||
0x30, 0x04, (byte) 0x98, 0x2D, 0x08, (byte) 0x94
|
||||
});
|
||||
|
||||
assertNotNull(data);
|
||||
assertEquals(59.0f, data.cadence().getRPM(), 0.01f);
|
||||
assertNull(data.speed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_malformedVarintDoesNotConsumeNextFrameStartByte() {
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(new byte[] {
|
||||
// Truncated Bosch speed frame: the varint continuation bit is set, but the frame ends
|
||||
// before the next varint byte. The following frame start byte must not be consumed.
|
||||
0x30, 0x04, (byte) 0x98, 0x2D, 0x08, (byte) 0x94,
|
||||
// Bosch cadence frame: message 0x985A with value 0x76
|
||||
0x30, 0x04, (byte) 0x98, 0x5A, 0x08, 0x76
|
||||
});
|
||||
|
||||
assertNotNull(data);
|
||||
assertNull(data.speed());
|
||||
assertEquals(59.0f, data.cadence().getRPM(), 0.01f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_leadingNoise_ignored() {
|
||||
byte[] raw = new byte[] {
|
||||
// Noise bytes to verify the parser resynchronizes on the Bosch frame start byte 0x30.
|
||||
0x01, 0x02, 0x03,
|
||||
// Bosch cadence frame: message 0x985A with value 0x76
|
||||
0x30, 0x04, (byte) 0x98, 0x5A, 0x08, 0x76
|
||||
};
|
||||
|
||||
BoschEbikeParser.Data data = BoschEbikeParser.parse(raw);
|
||||
|
||||
assertNotNull(data);
|
||||
assertEquals(59.0f, data.cadence().getRPM(), 0.01f);
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package de.dennisguse.opentracks.sensors.driver;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class CyclingPowerBluetoothTest {
|
||||
|
||||
@Test
|
||||
public void parseCyclingPower_power() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingPowerBluetooth.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0, 0, 40, 0});
|
||||
|
||||
// when
|
||||
CyclingPowerBluetooth.Data powerCadence = CyclingPowerBluetooth.parseCyclingPower(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(40, powerCadence.power().getW(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCyclingPower_power_with_cadence() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CyclingPowerBluetooth.CYCLING_POWER.serviceUUID(), 0, 0);
|
||||
characteristic.setValue(new byte[]{0x2C, 0x00, 0x00, 0x00, (byte) 0x9F, 0x00, 0x0C, 0x00, (byte) 0xE5, 0x42});
|
||||
|
||||
// when
|
||||
CyclingPowerBluetooth.Data powerCadence = CyclingPowerBluetooth.parseCyclingPower(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(0, powerCadence.power().getW(), 0.01);
|
||||
|
||||
assertEquals(12, powerCadence.crank().crankRevolutionsCount());
|
||||
assertEquals(17125, powerCadence.crank().crankRevolutionsTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsePayload_boschPower() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BoschEbikeParser.BOSCH_EBIKE.measurementUUID(), 0, 0);
|
||||
// Bosch human power frame: message 0x985B with value 0x7B => 123 W.
|
||||
characteristic.setValue(new byte[] { 0x30, 0x04, (byte) 0x98, 0x5B, 0x08, 0x7B });
|
||||
|
||||
CyclingPowerBluetooth.Data power = new CyclingPowerBluetooth().parsePayload(BoschEbikeParser.BOSCH_EBIKE, null, characteristic);
|
||||
|
||||
assertEquals(123, power.power().getW(), 0.01);
|
||||
assertNull(power.crank());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsePayload_boschCadenceOnly_returnsNull() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BoschEbikeParser.BOSCH_EBIKE.measurementUUID(), 0, 0);
|
||||
// Bosch cadence-only frame: message 0x985A with value 0x01B8 => 220 / 2 = 110 rpm, so power parsing should ignore it.
|
||||
characteristic.setValue(new byte[] { 0x30, 0x05, (byte) 0x98, 0x5A, 0x08, (byte) 0xB8, 0x01 });
|
||||
|
||||
CyclingPowerBluetooth.Data power = new CyclingPowerBluetooth().parsePayload(BoschEbikeParser.BOSCH_EBIKE, null, characteristic);
|
||||
|
||||
assertNull(power);
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -11,7 +11,7 @@ public class AggregatorBarometerTest {
|
||||
|
||||
private static void addSensorValue(AggregatorBarometer aggregatorBarometer, float[] values) {
|
||||
for (float f : values) {
|
||||
aggregatorBarometer.add(Instant.MIN, AtmosphericPressure.ofHPA(f));
|
||||
aggregatorBarometer.add(new Raw<>(Instant.MIN, AtmosphericPressure.ofHPA(f)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class AggregatorBarometerTest {
|
||||
addSensorValue(subject, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f});
|
||||
|
||||
// then
|
||||
Assert.assertEquals(0f, subject.output.gain_m(), 0.01);
|
||||
Assert.assertEquals(15f, subject.output.loss_m(), 0.01);
|
||||
Assert.assertEquals(0f, subject.aggregatedValue.gain_m(), 0.01);
|
||||
Assert.assertEquals(15f, subject.aggregatedValue.loss_m(), 0.01);
|
||||
}
|
||||
}
|
||||
+22
-40
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
@@ -13,9 +12,10 @@ import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.UintUtils;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingCadenceBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.driver.CyclingDistanceSpeedBluetooth;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SensorDataCyclingTest {
|
||||
@@ -25,8 +25,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 2048));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 2048)));
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||
@@ -37,8 +37,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 6184));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 8016));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 6184)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 8016)));
|
||||
|
||||
// then
|
||||
assertEquals(33.53, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||
@@ -49,8 +49,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 2048));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 2048)));
|
||||
|
||||
// then
|
||||
assertEquals(Cadence.of(0), current.getAggregatedValue(Instant.MIN));
|
||||
@@ -62,8 +62,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 1024));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 1024)));
|
||||
|
||||
// then
|
||||
assertFalse(current.hasReceivedData()); //TODO Cadence should be 0?
|
||||
@@ -74,8 +74,8 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1, UintUtils.UINT16_MAX - 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(2, 0));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(1, UintUtils.UINT16_MAX - 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(2, 0)));
|
||||
|
||||
// then
|
||||
assertEquals(60, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||
@@ -87,32 +87,13 @@ public class SensorDataCyclingTest {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
// when
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(UintUtils.UINT32_MAX - 1, 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(0, 2048));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(UintUtils.UINT32_MAX - 1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingCadence.CrankData(0, 2048)));
|
||||
|
||||
// then
|
||||
// TODO See #953
|
||||
// assertEquals(60, current.getValue().getRPM(), 0.01);
|
||||
assertNull(current.getAggregatedValue(Instant.MIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_cadence_directCadence_withoutPrevious() {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.DirectCadenceData(Cadence.of(92)));
|
||||
|
||||
assertEquals(92, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compute_cadence_directCadence_winsOverCrankData() {
|
||||
AggregatorCyclingCadence current = new AggregatorCyclingCadence("", "");
|
||||
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.CrankData(1L, 1024));
|
||||
current.add(Instant.MIN, new CyclingCadenceBluetooth.DirectCadenceData(Cadence.of(92)));
|
||||
|
||||
assertEquals(92, current.getAggregatedValue(Instant.MIN).getRPM(), 0.01);
|
||||
assertEquals(Cadence.of(0), current.getAggregatedValue(Instant.MIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,8 +102,8 @@ public class SensorDataCyclingTest {
|
||||
current.setWheelCircumference(Distance.ofMM(2150));
|
||||
|
||||
// when
|
||||
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(1, 6184));
|
||||
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(2, 8016));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(1, 6184)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(2, 8016)));
|
||||
|
||||
// then
|
||||
assertEquals(2.15, current.getAggregatedValue(Instant.MIN).distance().toM(), 0.01);
|
||||
@@ -136,13 +117,14 @@ public class SensorDataCyclingTest {
|
||||
current.setWheelCircumference(Distance.ofMM(2000));
|
||||
|
||||
// when
|
||||
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(UintUtils.UINT32_MAX - 1, 1024));
|
||||
current.add(Instant.MIN, new CyclingDistanceSpeedBluetooth.WheelData(0, 2048));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(UintUtils.UINT32_MAX - 1, 1024)));
|
||||
current.add(new Raw<>(Instant.MIN, new BluetoothHandlerCyclingDistanceSpeed.WheelData(0, 2048)));
|
||||
|
||||
|
||||
// then
|
||||
// TODO See #953
|
||||
// assertEquals(2, current.getValue().getDistance().toM(), 0.01);
|
||||
// assertEquals(2, current.getValue().getSpeed().toMPS(), 0.01);
|
||||
assertNull(current.getAggregatedValue(Instant.MIN));
|
||||
assertEquals(new AggregatorCyclingDistanceSpeed.Data(Distance.ZERO, Distance.ZERO, Speed.ZERO), current.getAggregatedValue(Instant.MIN));
|
||||
}
|
||||
}
|
||||
+37
-56
@@ -1,24 +1,24 @@
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
@@ -29,24 +29,32 @@ import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class TrackRecordingServiceNotificationManagerTest {
|
||||
|
||||
private static final String CHANNEL_ID = "TrackRecordingServiceNotificationManagerTest";
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
notificationManager.cancelAll();
|
||||
}
|
||||
@Mock
|
||||
private NotificationCompat.Builder notificationCompatBuilder;
|
||||
|
||||
@Mock
|
||||
private NotificationManager notificationManager;
|
||||
|
||||
@Test
|
||||
public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() {
|
||||
notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
|
||||
TrackPoint trackPointMock = new TrackPoint(TrackPoint.Type.TRACKPOINT,
|
||||
new Position(
|
||||
null,
|
||||
null, null, Distance.of(999f),
|
||||
Altitude.WGS84.of(10), null,
|
||||
null,
|
||||
Speed.ZERO)
|
||||
);
|
||||
|
||||
TrackingNotificationBuilder notificationBuilder = new TrackingNotificationBuilder(context, CHANNEL_ID);
|
||||
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationBuilder);
|
||||
when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean()))
|
||||
.thenReturn(notificationCompatBuilder);
|
||||
|
||||
TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder);
|
||||
subject.setUnitSystem(UnitSystem.METRIC);
|
||||
|
||||
Track track = new Track(
|
||||
@@ -60,43 +68,16 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
Statistics.DEFAULT
|
||||
);
|
||||
|
||||
subject.updateTrackPoint(context, createRecordingData(track, Distance.of(999f)), Distance.of(100));
|
||||
subject.updateTrackPoint(context, createRecordingData(track, Distance.of(999f)), Distance.of(100));
|
||||
subject.updateTrackPoint(context, createRecordingData(track, Distance.of(999f)), Distance.of(1000));
|
||||
subject.updateTrackPoint(context, createRecordingData(track, Distance.of(999f)), Distance.of(100));
|
||||
assertEquals(java.util.Arrays.asList(false, true, true, true, true, true, false, true), notificationBuilder.onlyAlertOnceValues);
|
||||
RecordingData data = new RecordingData(track, trackPointMock, null, null);
|
||||
|
||||
// when
|
||||
subject.updateTrackPoint(context, data, Distance.of(100));
|
||||
subject.updateTrackPoint(context, data, Distance.of(100));
|
||||
subject.updateTrackPoint(context, data, Distance.of(1000));
|
||||
subject.updateTrackPoint(context, data, Distance.of(100));
|
||||
|
||||
// then
|
||||
verify(notificationCompatBuilder, times(6)).setOnlyAlertOnce(true);
|
||||
verify(notificationCompatBuilder, times(2)).setOnlyAlertOnce(false);
|
||||
}
|
||||
|
||||
private RecordingData createRecordingData(Track track, Distance horizontalAccuracy) {
|
||||
TrackPoint trackPoint = new TrackPoint(
|
||||
TrackPoint.Type.TRACKPOINT,
|
||||
new Position(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
horizontalAccuracy,
|
||||
Altitude.WGS84.of(10),
|
||||
null,
|
||||
null,
|
||||
Speed.ZERO
|
||||
)
|
||||
);
|
||||
return new RecordingData(track, trackPoint, null, null);
|
||||
}
|
||||
|
||||
private static final class TrackingNotificationBuilder extends NotificationCompat.Builder {
|
||||
private final List<Boolean> onlyAlertOnceValues = new ArrayList<>();
|
||||
|
||||
private TrackingNotificationBuilder(Context context, String channelId) {
|
||||
super(context, channelId);
|
||||
setSmallIcon(R.drawable.ic_logo_color_24dp);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public NotificationCompat.Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
|
||||
onlyAlertOnceValues.add(onlyAlertOnce);
|
||||
return super.setOnlyAlertOnce(onlyAlertOnce);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-66
@@ -2,11 +2,11 @@ package de.dennisguse.opentracks.services;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -44,12 +44,12 @@ import de.dennisguse.opentracks.data.models.Statistics;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerRunningSpeedAndCadence;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
|
||||
import de.dennisguse.opentracks.sensors.driver.RunningSpeedAndCadenceBluetooth;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorBarometer;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorRunning;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@@ -94,41 +94,6 @@ public class TrackRecordingServiceRecordingTest {
|
||||
service = startService();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void reCreate_not_recording() {
|
||||
// given
|
||||
TrackIdStorage.unset(context);
|
||||
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
// when
|
||||
service.onStartCommand(null, 0, 1);
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void reCreate_recording() {
|
||||
// given
|
||||
assertFalse(service.isRecording());
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
assertTrue(service.isRecording());
|
||||
|
||||
// when (crash and restart)
|
||||
service.onDestroy();
|
||||
service.onCreate();
|
||||
service.onStartCommand(null, 0, 1);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
assertEquals(trackId, service.getRecordingStatusObservable().getValue().trackId());
|
||||
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void recording_startStop() {
|
||||
@@ -189,8 +154,7 @@ public class TrackRecordingServiceRecordingTest {
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
//We do not want the real GPS to interfere.
|
||||
GpsInternal gpsInternal = (GpsInternal)trackPointCreator.getSensorManager().getGpsHandler().driver;
|
||||
LocationManagerCompat.removeUpdates(gpsInternal.locationManager, gpsInternal.locationListenerCompat);
|
||||
LocationManagerCompat.removeUpdates((LocationManager) context.getSystemService(Context.LOCATION_SERVICE), trackPointCreator.getSensorManager().getGpsManager());
|
||||
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
|
||||
@@ -375,21 +339,16 @@ public class TrackRecordingServiceRecordingTest {
|
||||
mockAltitudeChange(trackPointCreator, 0);
|
||||
|
||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||
sensorManager.sensorDataAggregator.add(new AggregatorHeartRate("", ""));
|
||||
// when
|
||||
String sensor1 = "2020-02-02T02:02:03Z";
|
||||
trackPointCreator.setClock(sensor1);
|
||||
|
||||
AggregatorHeartRate avgHeartRate = new AggregatorHeartRate("", "");
|
||||
sensorManager.setAggregator(avgHeartRate);
|
||||
|
||||
//Should be ignored
|
||||
avgHeartRate.add(trackPointCreator.getNow(), HeartRate.of(5));
|
||||
sensorManager.onChange();
|
||||
sensorManager.onChanged(new Raw<>(sensor1, HeartRate.of(5))); //Should be ignored
|
||||
|
||||
String sensor3 = "2020-02-02T02:02:13Z";
|
||||
trackPointCreator.setClock(sensor3);
|
||||
avgHeartRate.add(trackPointCreator.getNow(), HeartRate.of(7));
|
||||
sensorManager.onChange();
|
||||
sensorManager.onChanged(new Raw<>(sensor3, HeartRate.of(7)));
|
||||
|
||||
String stopTime = "2020-02-02T02:02:15Z";
|
||||
trackPointCreator.setClock(stopTime);
|
||||
@@ -849,22 +808,19 @@ public class TrackRecordingServiceRecordingTest {
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
|
||||
AggregatorRunning aggregatorRunning = new AggregatorRunning("", "");
|
||||
SensorManager sensorManager = trackPointCreator.getSensorManager();
|
||||
sensorManager.setAggregator(aggregatorRunning);
|
||||
sensorManager.sensorDataAggregator.add(new AggregatorRunning("", ""));
|
||||
sensorManager.sensorDataAggregator.barometer = null;
|
||||
|
||||
// when
|
||||
String sensor1 = "2020-02-02T02:02:03Z";
|
||||
trackPointCreator.setClock(sensor1);
|
||||
//Should be ignored
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(1), Distance.ZERO));
|
||||
sensorManager.onChange();
|
||||
sensorManager.onChanged(new Raw<>(sensor1, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(1), Distance.ZERO))); //Should be ignored
|
||||
|
||||
// when
|
||||
String sensor2 = "2020-02-02T02:02:04Z";
|
||||
trackPointCreator.setClock(sensor2);
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(2), Distance.of(2)));
|
||||
sensorManager.onChange();
|
||||
sensorManager.onChanged(new Raw<>(sensor2, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(2), Distance.of(2))));
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:05Z";
|
||||
@@ -873,15 +829,12 @@ public class TrackRecordingServiceRecordingTest {
|
||||
// when
|
||||
String sensor3 = "2020-02-02T02:02:06Z";
|
||||
trackPointCreator.setClock(sensor3);
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(3), Distance.of(12)));
|
||||
sensorManager.onChange();
|
||||
sensorManager.onChanged(new Raw<>(sensor3, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(3), Distance.of(12))));
|
||||
|
||||
// when
|
||||
String sensor4 = "2020-02-02T02:02:07Z";
|
||||
trackPointCreator.setClock(sensor4);
|
||||
//Should be ignored
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(4), Distance.of(14)));
|
||||
sensorManager.onChange();
|
||||
sensorManager.onChanged(new Raw<>(sensor4, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(4), Distance.of(14)))); //Should be ignored
|
||||
|
||||
// when
|
||||
String gps2 = "2020-02-02T02:02:08Z";
|
||||
@@ -890,9 +843,7 @@ public class TrackRecordingServiceRecordingTest {
|
||||
// when
|
||||
String sensor5 = "2020-02-02T02:02:10Z";
|
||||
trackPointCreator.setClock(sensor5);
|
||||
//Should be ignored
|
||||
aggregatorRunning.add(trackPointCreator.getNow(), new RunningSpeedAndCadenceBluetooth.Data(Speed.of(5), Cadence.of(5), Distance.of(16)));
|
||||
sensorManager.onChange();
|
||||
sensorManager.onChanged(new Raw<>(sensor5, new BluetoothHandlerRunningSpeedAndCadence.Data(Speed.of(5), Cadence.of(5), Distance.of(16)))); //Should be ignored
|
||||
|
||||
// when
|
||||
String gps3 = "2020-02-02T02:02:12Z";
|
||||
@@ -1011,7 +962,7 @@ public class TrackRecordingServiceRecordingTest {
|
||||
}
|
||||
|
||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
|
||||
trackPointCreator.getSensorManager().getAltitudeChangeHandler().setAggregator(new AggregatorBarometer("", "") {
|
||||
trackPointCreator.getSensorManager().sensorDataAggregator.barometer = new AggregatorBarometer("", "") {
|
||||
@Override
|
||||
public boolean hasReceivedData() {
|
||||
return true;
|
||||
@@ -1022,7 +973,7 @@ public class TrackRecordingServiceRecordingTest {
|
||||
public AltitudeGainLoss getAggregatedValue(Instant now) {
|
||||
return new AltitudeGainLoss(altitudeGain, altitudeGain);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
|
||||
@@ -1034,6 +985,6 @@ public class TrackRecordingServiceRecordingTest {
|
||||
location.setSpeed(speed);
|
||||
|
||||
trackPointCreator.setClock(time);
|
||||
trackPointCreator.getSensorManager().getGpsHandler().onDataReceived(location);
|
||||
trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-39
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import static de.dennisguse.opentracks.util.LiveDataTestUtils.waitForValue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
@@ -42,7 +41,6 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@@ -58,8 +56,6 @@ import de.dennisguse.opentracks.sensors.GpsStatusValue;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
private static final Duration WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@@ -127,7 +123,6 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
service.tryStartSensors();
|
||||
waitUntilGpsStatusIs(GpsStatusValue.GPS_ENABLED);
|
||||
|
||||
// then
|
||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||
@@ -136,12 +131,11 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
service.stopSensors();
|
||||
waitUntilGpsStatusIs(GpsStatusValue.GPS_NONE);
|
||||
|
||||
// then
|
||||
assertEquals(TrackRecordingService.STATUS_DEFAULT, service.getRecordingStatusObservable().getValue());
|
||||
assertEquals(RecordingData.NOT_RECORDING, service.getRecordingDataObservable().getValue());
|
||||
assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue());
|
||||
// assertEquals(GpsStatusValue.GPS_NONE, service.getGpsStatusObservable().getValue()); TODO BUG: GPS can only be stopped if a service is recording.
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -152,9 +146,6 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
waitUntilRecordingStatusIs(new RecordingStatus(trackId));
|
||||
waitUntilRecordingDataIsNotDefault();
|
||||
waitUntilGpsStatusIs(GpsStatusValue.GPS_ENABLED);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
@@ -163,13 +154,9 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
assertEquals(GpsStatusValue.GPS_ENABLED, service.getGpsStatusObservable().getValue());
|
||||
|
||||
service.endCurrentTrack();
|
||||
waitUntilRecordingStatusIs(TrackRecordingService.STATUS_DEFAULT);
|
||||
waitUntilGpsStatusIs(GpsStatusValue.GPS_NONE);
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
waitUntilRecordingStatusIs(new RecordingStatus(trackId));
|
||||
waitUntilGpsStatusIs(GpsStatusValue.GPS_ENABLED);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
@@ -180,8 +167,6 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
service.endCurrentTrack();
|
||||
waitUntilRecordingStatusIs(TrackRecordingService.STATUS_DEFAULT);
|
||||
waitUntilGpsStatusIs(GpsStatusValue.GPS_NONE);
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
@@ -192,8 +177,6 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
|
||||
// when
|
||||
service.resumeTrack(trackId);
|
||||
waitUntilRecordingStatusIs(new RecordingStatus(trackId));
|
||||
waitUntilGpsStatusIs(GpsStatusValue.GPS_ENABLED);
|
||||
|
||||
// then
|
||||
assertTrue(service.isRecording());
|
||||
@@ -271,25 +254,4 @@ public class TrackRecordingServiceStateMachineTest {
|
||||
assertNotNull(trackId);
|
||||
assertNull(newTrackId);
|
||||
}
|
||||
|
||||
private void waitUntilRecordingDataIsNotDefault() {
|
||||
waitForValue(service.getRecordingDataObservable(),
|
||||
recordingData -> !RecordingData.NOT_RECORDING.equals(recordingData),
|
||||
WAIT_TIMEOUT,
|
||||
"recording data to leave NOT_RECORDING");
|
||||
}
|
||||
|
||||
private void waitUntilGpsStatusIs(GpsStatusValue expected) {
|
||||
waitForValue(service.getGpsStatusObservable(),
|
||||
expected::equals,
|
||||
WAIT_TIMEOUT,
|
||||
"gps status to become " + expected);
|
||||
}
|
||||
|
||||
private void waitUntilRecordingStatusIs(RecordingStatus expected) {
|
||||
waitForValue(service.getRecordingStatusObservable(),
|
||||
expected::equals,
|
||||
WAIT_TIMEOUT,
|
||||
"recording status to become " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static de.dennisguse.opentracks.services.TrackRecordingService.RECORDING_DATA_UPDATE_INTERVAL;
|
||||
import static de.dennisguse.opentracks.util.LiveDataTestUtils.waitForValue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.TestUtil;
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.io.file.importer.ExportImportTest;
|
||||
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
||||
import de.dennisguse.opentracks.sensors.GpsHandler;
|
||||
import de.dennisguse.opentracks.sensors.driver.GpsInternal;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackRecordingServiceUITest {
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
|
||||
private static final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDataForUI_gps() throws TimeoutException {
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
RecordingData recordingData;
|
||||
|
||||
// given
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:02Z");
|
||||
|
||||
// when
|
||||
service.startNewTrack();
|
||||
disconnectRealGps(trackPointCreator.getSensorManager().getGpsHandler());
|
||||
trackPointCreator.getSensorManager().reset();
|
||||
RecordingData initialRecordingData = waitUntilLatestTrackPointMatches(service, Instant.parse("2020-02-02T02:02:02Z"), false);
|
||||
|
||||
// then
|
||||
TrackPoint initialTrackPoint = initialRecordingData.latestTrackPoint();
|
||||
assertEquals(TrackPoint.Type.TRACKPOINT, initialTrackPoint.type());
|
||||
assertEquals(Instant.parse("2020-02-02T02:02:02Z"), initialTrackPoint.getTime());
|
||||
|
||||
|
||||
// when
|
||||
// In this step, the data is saved (and Aggregator.resetAggregated called), but data should still be shown in the UI.
|
||||
trackPointCreator.setClock("2020-02-02T02:02:03Z");
|
||||
ExportImportTest.sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 1f);
|
||||
recordingData = waitUntilLatestTrackPointMatches(service, Instant.parse("2020-02-02T02:02:03Z"), true);
|
||||
|
||||
// then
|
||||
new TrackPointAssert().assertEquals(new TrackPoint(
|
||||
null,
|
||||
TrackPoint.Type.TRACKPOINT,
|
||||
new Position(
|
||||
Instant.parse("2020-02-02T02:02:03Z"),
|
||||
3.1234567, 14.0014567, null,
|
||||
Altitude.EGM2008.of(1013.05), null,
|
||||
null,
|
||||
Speed.of(15)),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new AltitudeGainLoss(1, 1)
|
||||
),
|
||||
recordingData.latestTrackPoint());
|
||||
}
|
||||
|
||||
private void disconnectRealGps(GpsHandler gpsHandler) {
|
||||
if (gpsHandler.driver != null) {
|
||||
gpsHandler.driver.disconnect();
|
||||
}
|
||||
gpsHandler.onConnected(null, GpsInternal.LOCATION_PROVIDER);
|
||||
}
|
||||
|
||||
private RecordingData waitUntilLatestTrackPointMatches(TrackRecordingService service, Instant expectedTime, boolean requiresLocation) {
|
||||
return waitForValue(service.getRecordingDataObservable(),
|
||||
recordingData -> {
|
||||
TrackPoint latestTrackPoint = recordingData != null ? recordingData.latestTrackPoint() : null;
|
||||
return latestTrackPoint != null
|
||||
&& expectedTime.equals(latestTrackPoint.getTime())
|
||||
&& (!requiresLocation || latestTrackPoint.position().hasLocation());
|
||||
},
|
||||
RECORDING_DATA_UPDATE_INTERVAL.multipliedBy(3),
|
||||
"latest track point to reach " + expectedTime + (requiresLocation ? " with location" : ""));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user