forked from upstream-mirrors/OpenTracks
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 63e175c76a | |||
| 8142073abd | |||
| 149c620525 | |||
| c96b0e3617 | |||
| 268c27dc4a | |||
| 3146785498 | |||
| 11589dfdd3 | |||
| 96828febdb | |||
| 98ae6fe0d2 | |||
| 2e27b06c01 | |||
| 8db8b58020 | |||
| 9a9e1e6947 | |||
| d9880f259d | |||
| 08b5ab381e | |||
| a63298ef6e | |||
| 1294e944a7 | |||
| 7258335180 | |||
| 139d72affe | |||
| 3791ccf888 | |||
| dc78615c83 | |||
| 53dfa915fb | |||
| 7c6141f98c | |||
| f8279c7095 | |||
| 465e3e9be8 | |||
| f38999a970 | |||
| 4dfd2485bf | |||
| 10273cd772 | |||
| fbfe6f3ab8 | |||
| 86726c5e0e | |||
| b711c4fdc5 | |||
| cefdb1280a | |||
| 91b379c328 | |||
| e28a84fc88 | |||
| c2d8d06204 | |||
| 7ca871c699 | |||
| 337c50eb91 | |||
| 2ff8d21897 | |||
| 2d4e987812 | |||
| fb928ccc45 | |||
| ab8791a620 | |||
| 7c03085761 | |||
| 12a8686896 | |||
| e12bb967e6 | |||
| d56b018676 | |||
| a6fd701004 | |||
| ce26e20571 | |||
| d3ff2061c6 | |||
| aa63474114 | |||
| cf545dc17b | |||
| a542f45661 | |||
| 1eeb706330 | |||
| 01ea65742b | |||
| e9456919cb | |||
| 2a8e1c99b6 | |||
| 245854581b | |||
| e372a780f0 | |||
| 25a2f978c4 | |||
| 36c3885dce | |||
| 00be0b565c | |||
| 8b1e1cf294 | |||
| ce313ba4fd | |||
| df501bde0d | |||
| 30ed8d8ba3 | |||
| ea2127846d | |||
| ac0da5e9d1 | |||
| 47fea690c9 | |||
| a1fa95094f | |||
| c4a1fb0d98 | |||
| 57cd4b3780 | |||
| 5c645712ff | |||
| e724baaafa | |||
| f55dcd7162 | |||
| 74169cb819 | |||
| 1821f47cf1 | |||
| 925a253bbb | |||
| e89ae1c2df | |||
| 75058118e0 | |||
| 96559230f8 | |||
| f16f2cfc64 | |||
| 7f353a5d13 | |||
| 2453edaa65 | |||
| 1b1568511a | |||
| 29a36c3ea7 | |||
| 71d0c7cc92 | |||
| b09d640844 | |||
| a039f1a00c | |||
| 3df5c06c20 | |||
| 2904c65a01 | |||
| 4977789004 | |||
| 95e6448cba | |||
| a66c1b9407 | |||
| 9d55ac261f | |||
| dbd3e2fc5c | |||
| 713e454e44 | |||
| f0616fc043 | |||
| 75ed3120bd | |||
| b22e41cf66 | |||
| d1f003c0ce | |||
| 6ef80ade98 | |||
| e49d9fbd83 | |||
| c7738b1cb2 | |||
| 713f5c7708 | |||
| b9a36d1b3c | |||
| 34e89ea3f9 | |||
| 50a0e37086 | |||
| 11ac6b7279 | |||
| aadb79bf7d | |||
| da6b26644e | |||
| b264655d75 | |||
| 1ea9c0b216 | |||
| 8040edac7a | |||
| 037c79a1c5 | |||
| bc46f7447e |
@@ -1,36 +0,0 @@
|
||||
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
|
||||
@@ -0,0 +1,56 @@
|
||||
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!"
|
||||
@@ -0,0 +1,78 @@
|
||||
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
@@ -0,0 +1,6 @@
|
||||
<?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
@@ -0,0 +1,23 @@
|
||||
<?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
@@ -0,0 +1,8 @@
|
||||
<?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
@@ -0,0 +1,10 @@
|
||||
<?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
@@ -0,0 +1,17 @@
|
||||
<?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,6 +45,18 @@ 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,6 +8,7 @@
|
||||
* 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)
|
||||
|
||||
+14
-12
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:9.2.1'
|
||||
classpath 'com.android.tools.build:gradle:9.4.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,9 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'de.dennisguse.opentracks'
|
||||
versionCode 6669
|
||||
versionName "v4.27.1"
|
||||
applicationId 'de.dennisguse.opentracks.fork'
|
||||
versionCode 6751
|
||||
versionName "v4.28.3"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -98,6 +98,7 @@ android {
|
||||
release {
|
||||
crunchPngs = false
|
||||
minifyEnabled = false
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,18 +107,18 @@ android {
|
||||
productFlavors {
|
||||
nightly {
|
||||
dimension 'version'
|
||||
applicationId 'de.dennisguse.opentracks.nightly'
|
||||
applicationId 'de.dennisguse.opentracks.fork.nightly'
|
||||
|
||||
versionCode getVersionCodeNightly()
|
||||
versionName getVersionName()
|
||||
base.archivesName = "de.dennisguse.opentracks.nightly_${getVersionCodeNightly()}"
|
||||
base.archivesName = "de.dennisguse.opentracks.fork.nightly_${getVersionCodeNightly()}"
|
||||
|
||||
signingConfig = signingConfigs.nightly
|
||||
}
|
||||
irreproducible {
|
||||
// Non-reproducible: https://f-droid.org/de/packages/de.dennisguse.opentracks/
|
||||
dimension 'version'
|
||||
applicationId 'de.dennisguse.opentracks'
|
||||
applicationId 'de.dennisguse.opentracks.fork'
|
||||
versionNameSuffix 'irreproducible'
|
||||
}
|
||||
reproducible {
|
||||
@@ -125,9 +126,9 @@ 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.playstore'
|
||||
applicationId 'de.dennisguse.opentracks.fork.playstore'
|
||||
|
||||
base.archivesName = "de.dennisguse.opentracks.playstore_${getVersionName()}_${android.defaultConfig.versionCode}"
|
||||
base.archivesName = "de.dennisguse.opentracks.fork.playstore_${getVersionName()}_${android.defaultConfig.versionCode}"
|
||||
|
||||
signingConfig = signingConfigs.release
|
||||
}
|
||||
@@ -145,17 +146,18 @@ android {
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.8.0'
|
||||
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.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.2'
|
||||
implementation 'androidx.core:core:1.19.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-rc01'
|
||||
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'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.7.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
v4.27.2: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Not all valid timezone offsets could be set
|
||||
- Fixed race condition for IDLE trackpoints
|
||||
@@ -0,0 +1,10 @@
|
||||
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)
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
v4.28.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- HealthConnect: sending total distance, speed and elevation gain
|
||||
|
||||
Bugfix:
|
||||
- Gravel activity was not selectable
|
||||
|
||||
Developer:
|
||||
- Cleanup ContentProvider URLs
|
||||
@@ -0,0 +1,7 @@
|
||||
v4.28.2: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Import: ignore GPX waypoints without time
|
||||
|
||||
Bugfix:
|
||||
- Only start GPS once
|
||||
@@ -0,0 +1,10 @@
|
||||
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
|
||||
|
||||
Primer lanzamiento de OpenTracks (es decir, una bifurcación de MyTracks)
|
||||
Primera versión de OpenTracks (es decir, una bifurcación dura de MyTracks)
|
||||
|
||||
Cambios:
|
||||
- Cambio de nombre de MyTracks a OpenTracks
|
||||
- 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)
|
||||
- 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)
|
||||
|
||||
@@ -13,5 +13,5 @@ Correcciones de errores:
|
||||
- Se ha corregido la exportación a KML/KMZ
|
||||
|
||||
Desarrollador:
|
||||
- Las pruebas son ejecutables (pero muchas fallan)
|
||||
- Las pruebas se pueden ejecutar (aunque muchas fallan)
|
||||
- Se ha habilitado la integración continua con TravisCI (sin pruebas)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
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
|
||||
@@ -0,0 +1,16 @@
|
||||
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
|
||||
@@ -0,0 +1,7 @@
|
||||
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)
|
||||
@@ -0,0 +1,13 @@
|
||||
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)
|
||||
@@ -0,0 +1,14 @@
|
||||
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
|
||||
@@ -0,0 +1,5 @@
|
||||
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
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
@@ -0,0 +1,8 @@
|
||||
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
|
||||
@@ -0,0 +1,10 @@
|
||||
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)
|
||||
@@ -0,0 +1,10 @@
|
||||
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
|
||||
@@ -0,0 +1,10 @@
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
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)
|
||||
@@ -0,0 +1,12 @@
|
||||
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
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
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
|
||||
@@ -0,0 +1,12 @@
|
||||
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)
|
||||
@@ -0,0 +1,14 @@
|
||||
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
|
||||
@@ -0,0 +1,10 @@
|
||||
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
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.6.1: OpenTracks
|
||||
|
||||
Corrección de errores:
|
||||
- Importación de KMZ: guardar la ruta correcta de la imagen
|
||||
@@ -0,0 +1,4 @@
|
||||
v3.6.2: OpenTracks
|
||||
|
||||
Corrección de errores:
|
||||
- Los gráficos no se mostraban correctamente (problema introducido en la v3.6.0)
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
@@ -0,0 +1,7 @@
|
||||
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
|
||||
@@ -0,0 +1,6 @@
|
||||
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
|
||||
@@ -0,0 +1,7 @@
|
||||
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
|
||||
@@ -0,0 +1,12 @@
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
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)
|
||||
@@ -0,0 +1,16 @@
|
||||
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
|
||||
@@ -0,0 +1,8 @@
|
||||
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
|
||||
@@ -0,0 +1,14 @@
|
||||
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:
|
||||
- Detección de inactividad se puede desactivar
|
||||
- Se puede desactivar la detección de inactividad
|
||||
|
||||
Correcciones de errores:
|
||||
- UI para añadir imágenes a los marcadores estaba oculto
|
||||
Corrección de errores:
|
||||
- La interfaz de usuario para añadir imágenes a los marcadores estaba oculta
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
OpenTracks es un tracker deportivo que respeta tu privacidad.
|
||||
Un compañero de seguimiento deportivo que respeta tu privacidad.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
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)
|
||||
@@ -0,0 +1,17 @@
|
||||
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)
|
||||
@@ -0,0 +1,14 @@
|
||||
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 @@
|
||||
Teman pelacakan olahraga yang menghormati privasi Anda.
|
||||
Sebuah teman pelacak olahraga yang benar-benar 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 yang ditampilkan dalam EGM2008 (di atas permukaan laut); diekspor sebagai WGS84.
|
||||
* Ketinggian 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 / Perangkat Lunak Bebas / Sumber Terbuka
|
||||
Perangkat Lunak Bebas / Sumber Terbuka
|
||||
yang berarti Anda dapat menggunakan, mempelajari, mengubah, dan berbagi kode sumber.
|
||||
Berlisensi Apache 2.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
Sebuah pelacak olahraga yang benar-benar menghormati privasi Anda.
|
||||
Sebuah teman pelacak olahraga yang benar-benar menghormati privasi Anda.
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
OpenTracks é um aplicativo de rastreamento de esportes que respeita totalmente a sua privacidade.
|
||||
Um companheiro de rastreamento esportivo que respeita sua privacidade.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -1 +1 @@
|
||||
一個尊重您的隱私的運動蹤跡紀錄器。
|
||||
一款尊重您隱私的運動蹤跡紀錄器。
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#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.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
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
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
cat src/main/java/de/dennisguse/opentracks/data/statistics/TrackStatisticsUpdater.java | grep -n "if (movingDistance != null) {" -B 2 -A 30
|
||||
@@ -0,0 +1,4 @@
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||
}
|
||||
rootProject.name = 'OpenTracks'
|
||||
@@ -5,10 +5,11 @@ import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
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 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.waitUntilDisplayed;
|
||||
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;
|
||||
@@ -16,7 +17,6 @@ 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 org.hamcrest.Matchers.allOf;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
@@ -27,7 +27,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
+3
-3
@@ -227,13 +227,13 @@ public class CustomContentProviderUtilsTest {
|
||||
|
||||
private void assertCount(int trackCount, int trackPointCount, int markerCount) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
try (Cursor tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID)) {
|
||||
try (Cursor tracksCursor = contentResolver.query(CustomContentProvider.UrlType.TRACKS.getUri(), null, null, null, TracksColumns._ID)) {
|
||||
assertEquals(trackCount, tracksCursor.getCount());
|
||||
}
|
||||
try (Cursor tracksPointsCursor = contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, null, null, null, TrackPointsColumns._ID)) {
|
||||
try (Cursor tracksPointsCursor = contentResolver.query(CustomContentProvider.UrlType.TRACKPOINTS_BY_ID.getUri(), null, null, null, TrackPointsColumns._ID)) {
|
||||
assertEquals(trackPointCount, tracksPointsCursor.getCount());
|
||||
}
|
||||
try (Cursor markerCursor = contentResolver.query(MarkerColumns.CONTENT_URI, null, null, null, MarkerColumns._ID)) {
|
||||
try (Cursor markerCursor = contentResolver.query(CustomContentProvider.UrlType.MARKERS.getUri(), null, null, null, MarkerColumns._ID)) {
|
||||
assertEquals(markerCount, markerCursor.getCount());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
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(" "));
|
||||
}
|
||||
}
|
||||
+180
-2
@@ -120,6 +120,184 @@ 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 {
|
||||
@@ -285,8 +463,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.gpx_timezone);
|
||||
try (InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone)) {
|
||||
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)) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
// when
|
||||
|
||||
+2
@@ -6,6 +6,7 @@ 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;
|
||||
|
||||
@@ -91,6 +92,7 @@ public class TrackRecordingServiceNotificationManagerTest {
|
||||
setSmallIcon(R.drawable.ic_logo_color_24dp);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public NotificationCompat.Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
|
||||
onlyAlertOnceValues.add(onlyAlertOnce);
|
||||
|
||||
+68
-17
@@ -92,7 +92,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -132,7 +135,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -172,7 +178,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -212,7 +221,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -254,7 +266,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -276,7 +291,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -316,7 +334,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -338,7 +359,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -378,7 +402,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -418,7 +445,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -458,7 +488,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -498,7 +531,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -520,7 +556,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -560,7 +599,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -582,7 +624,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -610,7 +655,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
@@ -644,7 +692,10 @@ public class VoiceAnnouncementUtilsTest {
|
||||
new SensorData<>(HeartRate.of(60), "unused"),
|
||||
null, null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// when
|
||||
|
||||
+5
-5
@@ -63,8 +63,8 @@ public class RecordingLayoutTest extends TestCase {
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsvLine(csv1, resources);
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(recordingLayout.getName(), "Default Layout");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
assertEquals("Default Layout", recordingLayout.getName());
|
||||
assertEquals(2, recordingLayout.getColumnsPerRow());
|
||||
assertEquals(PreferencesUtils.getDefaultLayout().getFields(), recordingLayout.getFields());
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ public class RecordingLayoutTest extends TestCase {
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(recordingLayout.getName(), "Default Layout");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
assertEquals("Default Layout", recordingLayout.getName());
|
||||
assertEquals(2, recordingLayout.getColumnsPerRow());
|
||||
assertEquals(PreferencesUtils.getDefaultLayout().getFields(), recordingLayout.getFields());
|
||||
}
|
||||
|
||||
@@ -144,6 +144,6 @@ public class RecordingLayoutTest extends TestCase {
|
||||
String csv = recordingLayout.toCsv();
|
||||
|
||||
// then csv is well built
|
||||
assertEquals(csv, "Test Layout;2;key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
|
||||
assertEquals("Test Layout;2;key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;", csv);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
|
||||
@@ -33,7 +34,7 @@ public final class ActivityTestUtils {
|
||||
|
||||
Application.ActivityLifecycleCallbacks callbacks = new Application.ActivityLifecycleCallbacks() {
|
||||
@Override
|
||||
public void onActivityResumed(Activity activity) {
|
||||
public void onActivityResumed(@NonNull Activity activity) {
|
||||
if (activityClass.isInstance(activity)) {
|
||||
resumedActivityRef.set(activityClass.cast(activity));
|
||||
latch.countDown();
|
||||
@@ -41,27 +42,27 @@ public final class ActivityTestUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
||||
public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) {
|
||||
public void onActivityStarted(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(Activity activity) {
|
||||
public void onActivityPaused(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {
|
||||
public void onActivityStopped(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
|
||||
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
public void onActivityDestroyed(@NonNull Activity activity) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.SystemClock;
|
||||
@@ -14,11 +14,11 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.test.espresso.PerformException;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.util.HumanReadables;
|
||||
import androidx.test.espresso.util.TreeIterables;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
|
||||
import androidx.test.runner.lifecycle.Stage;
|
||||
import androidx.test.espresso.util.HumanReadables;
|
||||
import androidx.test.espresso.util.TreeIterables;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
@@ -33,8 +33,6 @@ import java.util.function.Predicate;
|
||||
|
||||
public class EspressoUtils {
|
||||
|
||||
private static final long VIEW_POLLING_INTERVAL_MS = 50;
|
||||
|
||||
public static Matcher<View> withRecyclerViewItemCount(final int count) {
|
||||
return new TypeSafeMatcher<>() {
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||
<trk>
|
||||
<name>20210907_213924.gpx</name>
|
||||
<trkseg>
|
||||
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||
<ele>-5</ele>
|
||||
<hdop>4</hdop>
|
||||
</trkpt>
|
||||
<trkpt lat="30.14184657" lon="-40.38670089">
|
||||
<ele>-5</ele>
|
||||
<time/>
|
||||
<hdop>4</hdop>
|
||||
</trkpt>
|
||||
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||
<ele>-5</ele>
|
||||
<time>2021-09-07T22:12:00Z</time>
|
||||
<hdop>4</hdop>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
||||
+2
-1
@@ -28,10 +28,11 @@ xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/
|
||||
<opentracks:typeTranslated><![CDATA[unknown]]></opentracks:typeTranslated>
|
||||
<gpxtrkx:TrackStatsExtension>
|
||||
<gpxtrkx:Distance>0.0</gpxtrkx:Distance>
|
||||
<gpxtrkx:TimerTime>0</gpxtrkx:TimerTime>
|
||||
<gpxtrkx:TotalElapsedTime>0</gpxtrkx:TotalElapsedTime>
|
||||
<gpxtrkx:MovingTime>0</gpxtrkx:MovingTime>
|
||||
<gpxtrkx:StoppedTime>0</gpxtrkx:StoppedTime>
|
||||
<gpxtrkx:MaxSpeed>0.0</gpxtrkx:MaxSpeed>
|
||||
<gpxtrkx:MovingSpeed>0.0</gpxtrkx:MovingSpeed>
|
||||
</gpxtrkx:TrackStatsExtension>
|
||||
</extensions>
|
||||
<trkseg>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gpxpx="http://www.garmin.com/xmlschemas/PowerExtension/v1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||
<trk>
|
||||
<name>gpx with power</name>
|
||||
<trkseg>
|
||||
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||
<ele>-5</ele>
|
||||
<time>2021-09-07T22:10:19Z</time>
|
||||
<hdop>4</hdop>
|
||||
<extensions>
|
||||
<gpxpx:PowerInWatts>100</gpxpx:PowerInWatts>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
<trkpt lat="30.14184657" lon="-40.38670089">
|
||||
<ele>-5</ele>
|
||||
<time>2021-09-07T22:11:07Z</time>
|
||||
<hdop>4</hdop>
|
||||
<extensions>
|
||||
<gpxpx:PowerInWatts>110</gpxpx:PowerInWatts>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||
<ele>-5</ele>
|
||||
<time>2021-09-07T22:12:00Z</time>
|
||||
<hdop>4</hdop>
|
||||
<extensions>
|
||||
<gpxpx:PowerInWatts>150</gpxpx:PowerInWatts>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<gpx version="1.1" creator="Cruiser" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:pwr="http://www.garmin.com/xmlschemas/PowerExtension/v1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||
<trk>
|
||||
<name>gpx with power</name>
|
||||
<trkseg>
|
||||
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||
<ele>-5</ele>
|
||||
<time>2021-09-07T22:10:19Z</time>
|
||||
<hdop>4</hdop>
|
||||
<extensions>
|
||||
<pwr:PowerInWatts>100</pwr:PowerInWatts>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
<trkpt lat="30.14184657" lon="-40.38670089">
|
||||
<ele>-5</ele>
|
||||
<time>2021-09-07T22:11:07Z</time>
|
||||
<hdop>4</hdop>
|
||||
<extensions>
|
||||
<pwr:PowerInWatts>110</pwr:PowerInWatts>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
<trkpt lat="30.14185982" lon="-40.3863038">
|
||||
<ele>-5</ele>
|
||||
<time>2021-09-07T22:12:00Z</time>
|
||||
<hdop>4</hdop>
|
||||
<extensions>
|
||||
<pwr:PowerInWatts>150</pwr:PowerInWatts>
|
||||
</extensions>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
||||
@@ -69,6 +69,12 @@ limitations under the License.
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"
|
||||
android:minSdkVersion="33" />
|
||||
|
||||
<uses-permission android:name="android.permission.health.WRITE_EXERCISE" />
|
||||
<uses-permission android:name="android.permission.health.WRITE_EXERCISE_ROUTE" />
|
||||
<uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
|
||||
<uses-permission android:name="android.permission.health.WRITE_ELEVATION_GAINED" />
|
||||
<uses-permission android:name="android.permission.health.WRITE_SPEED" />
|
||||
|
||||
<application
|
||||
android:name=".Startup"
|
||||
android:allowBackup="true"
|
||||
@@ -126,9 +132,32 @@ limitations under the License.
|
||||
|
||||
<activity android:name=".AboutActivity" />
|
||||
|
||||
<!-- Permissions -->
|
||||
<!-- until Android 13 -->
|
||||
<activity
|
||||
android:name=".HelpActivity"
|
||||
android:label="@string/menu_help" />
|
||||
android:name=".HealthConnectPermissionsRationaleActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- for Android 14 -->
|
||||
<activity-alias
|
||||
android:name="ViewPermissionUsageActivity"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.START_VIEW_PERMISSION_USAGE"
|
||||
android:targetActivity=".HealthConnectPermissionsRationaleActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
|
||||
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity android:name=".settings.HealthConnect$Settings" />
|
||||
<activity android:name=".settings.HealthConnect$ConfigurePermissions" />
|
||||
<activity android:name=".settings.HealthConnect$ExportAll" />
|
||||
|
||||
<activity android:name=".HelpActivity" />
|
||||
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$ImportDirectoryChooserActivity" />
|
||||
|
||||
@@ -395,6 +424,16 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_logo_color_24dp"
|
||||
android:label="@string/recording_service" />
|
||||
|
||||
<service
|
||||
android:name=".services.MediaNotificationListenerService"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!--Necessary for SDK < 33 to (re-)store per app locale information-->
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
@@ -410,5 +449,6 @@ limitations under the License.
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TTS_SERVICE" />
|
||||
</intent>
|
||||
<package android:name="com.google.android.apps.healthdata" />
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import de.dennisguse.opentracks.databinding.ActivityHealthConnectPermissionsRationaleBinding;
|
||||
|
||||
public class HealthConnectPermissionsRationaleActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ActivityHealthConnectPermissionsRationaleBinding viewBinding = ActivityHealthConnectPermissionsRationaleBinding.inflate(getLayoutInflater());
|
||||
setSupportActionBar(viewBinding.healthConnectActivityToolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
setContentView(viewBinding.getRoot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -367,7 +367,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_show_on_map) {
|
||||
DataProvider.showTrackOnMap(this, false, trackIds);
|
||||
DataProvider.showTrackOnMap(this, recordingStatus.isRecording(), trackIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
|
||||
|
||||
private Snackbar snackbar;
|
||||
private Snackbar sensorWarningSnackbar;
|
||||
|
||||
// The following are setFrequency in onCreate
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
@@ -86,6 +87,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
service.getGpsStatusObservable()
|
||||
.observe(TrackRecordingActivity.this, this::onGpsStatusChanged);
|
||||
|
||||
service.getSensorDisconnectedWarningObservable()
|
||||
.observe(TrackRecordingActivity.this, this::onSensorDisconnectedWarningChanged);
|
||||
|
||||
if (!service.isRecording()) {
|
||||
finish();
|
||||
return;
|
||||
@@ -408,4 +412,27 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
});
|
||||
snackbar.show();
|
||||
}
|
||||
|
||||
private void onSensorDisconnectedWarningChanged(boolean show) {
|
||||
if (show) {
|
||||
if (sensorWarningSnackbar == null) {
|
||||
sensorWarningSnackbar = Snackbar
|
||||
.make(viewBinding.trackRecordingCoordinatorLayout,
|
||||
getString(R.string.sensor_disconnected_warning),
|
||||
Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(getString(R.string.sensor_disconnected_switch_action), v -> {
|
||||
if (trackRecordingServiceConnection != null && trackRecordingServiceConnection.getTrackRecordingService() != null) {
|
||||
trackRecordingServiceConnection.getTrackRecordingService().setOverrideSensorWithGPS(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!sensorWarningSnackbar.isShown()) {
|
||||
sensorWarningSnackbar.show();
|
||||
}
|
||||
} else {
|
||||
if (sensorWarningSnackbar != null && sensorWarningSnackbar.isShown()) {
|
||||
sensorWarningSnackbar.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import de.dennisguse.opentracks.data.models.TrackBuilder;
|
||||
import de.dennisguse.opentracks.databinding.TrackStoppedBinding;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportUtils;
|
||||
import de.dennisguse.opentracks.io.healthconnect.exporter.HealthConnectUtils;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment;
|
||||
@@ -93,6 +94,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
viewBinding.finishButton.setOnClickListener(v -> {
|
||||
storeTrackMetaData(contentProviderUtils, track);
|
||||
ExportUtils.postWorkoutExport(this, trackId);
|
||||
HealthConnectUtils.postExportTrack(this, trackId);
|
||||
finish();
|
||||
});
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -131,7 +132,7 @@ public class ContentProviderUtils {
|
||||
@VisibleForTesting
|
||||
public void deleteAllTracks(Context context) {
|
||||
// Delete tracks last since it triggers a database vacuum call
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
||||
contentResolver.delete(CustomContentProvider.UrlType.TRACKS.getUri(), null, null);
|
||||
|
||||
File dir = FileUtils.getPhotoDir(context);
|
||||
FileUtils.deleteDirectoryRecurse(dir);
|
||||
@@ -144,13 +145,13 @@ public class ContentProviderUtils {
|
||||
}
|
||||
|
||||
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, whereClause, trackIds.stream().map(trackId -> Long.toString(trackId.id())).toArray(String[]::new));
|
||||
contentResolver.delete(CustomContentProvider.UrlType.TRACKS.getUri(), whereClause, trackIds.stream().map(trackId -> Long.toString(trackId.id())).toArray(String[]::new));
|
||||
}
|
||||
|
||||
public void deleteTrack(Context context, @NonNull Track.Id trackId) {
|
||||
// Delete track folder resources.
|
||||
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.id())});
|
||||
contentResolver.delete(CustomContentProvider.UrlType.TRACKS.getUri(), TracksColumns._ID + "=?", new String[]{Long.toString(trackId.id())});
|
||||
}
|
||||
|
||||
//TODO Only use for tests; also move to tests.
|
||||
@@ -194,7 +195,7 @@ public class ContentProviderUtils {
|
||||
selectionArgs = new String[]{"%" + searchQuery + "%", "%" + searchQuery + "%", "%" + searchQuery + "%"};
|
||||
}
|
||||
|
||||
return new TrackListIterator(contentResolver.query(TracksColumns.CONTENT_URI, PROJECTION, selection, selectionArgs, sortOrder));
|
||||
return new TrackListIterator(contentResolver.query(CustomContentProvider.UrlType.TRACKS.getUri(), PROJECTION, selection, selectionArgs, sortOrder));
|
||||
}
|
||||
|
||||
public Track getTrack(@NonNull Track.Id trackId) {
|
||||
@@ -225,7 +226,7 @@ public class ContentProviderUtils {
|
||||
* @param sortOrder the sort order. Can be null
|
||||
*/
|
||||
Cursor getTrackCursor(String selection, String[] selectionArgs, String sortOrder) {
|
||||
return contentResolver.query(TracksColumns.CONTENT_URI, null, selection, selectionArgs, sortOrder);
|
||||
return contentResolver.query(CustomContentProvider.UrlType.TRACKS.getUri(), null, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,7 +238,7 @@ public class ContentProviderUtils {
|
||||
*/
|
||||
public Track.Id insertTrack(Track track) {
|
||||
//TODO Unset trackId
|
||||
Uri uri = contentResolver.insert(TracksColumns.CONTENT_URI, createContentValues(track));
|
||||
Uri uri = contentResolver.insert(CustomContentProvider.UrlType.TRACKS.getUri(), createContentValues(track));
|
||||
return new Track.Id(ContentUris.parseId(uri));
|
||||
}
|
||||
|
||||
@@ -248,7 +249,7 @@ public class ContentProviderUtils {
|
||||
* @param track the track
|
||||
*/
|
||||
public void updateTrack(Track track) {
|
||||
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.id().id())});
|
||||
contentResolver.update(CustomContentProvider.UrlType.TRACKS.getUri(), createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.id().id())});
|
||||
}
|
||||
|
||||
private ContentValues createContentValues(Track track) {
|
||||
@@ -270,7 +271,7 @@ public class ContentProviderUtils {
|
||||
}
|
||||
|
||||
public void updateTrackStatistics(@NonNull Track.Id trackId, @NonNull Statistics trackStatistics) {
|
||||
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(trackStatistics), TracksColumns._ID + "=?", new String[]{Long.toString(trackId.id())});
|
||||
contentResolver.update(CustomContentProvider.UrlType.TRACKS.getUri(), createContentValues(trackStatistics), TracksColumns._ID + "=?", new String[]{Long.toString(trackId.id())});
|
||||
}
|
||||
|
||||
private ContentValues createContentValues(Statistics trackStatistics) {
|
||||
@@ -362,7 +363,7 @@ public class ContentProviderUtils {
|
||||
public void deleteMarker(Context context, Marker.Id markerId) {
|
||||
final Marker marker = getMarker(markerId);
|
||||
deleteMarkerPhoto(context, marker);
|
||||
contentResolver.delete(MarkerColumns.CONTENT_URI, MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.id())});
|
||||
contentResolver.delete(CustomContentProvider.UrlType.MARKERS.getUri(), MarkerColumns._ID + "=?", new String[]{Long.toString(markerId.id())});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,7 +423,7 @@ public class ContentProviderUtils {
|
||||
if (updateMarker.photoUrl() == null) {
|
||||
deleteMarkerPhoto(context, savedMarker);
|
||||
}
|
||||
int rows = contentResolver.update(MarkerColumns.CONTENT_URI, createContentValues(updateMarker, updateMarker.trackId()), MarkerColumns._ID + "=?", new String[]{Long.toString(updateMarker.id().id())});
|
||||
int rows = contentResolver.update(CustomContentProvider.UrlType.MARKERS.getUri(), createContentValues(updateMarker, updateMarker.trackId()), MarkerColumns._ID + "=?", new String[]{Long.toString(updateMarker.id().id())});
|
||||
return rows == 1;
|
||||
}
|
||||
|
||||
@@ -430,7 +431,7 @@ public class ContentProviderUtils {
|
||||
assert marker.id() == null;
|
||||
assert marker.trackId() != null;
|
||||
|
||||
Uri uri = contentResolver.insert(MarkerColumns.CONTENT_URI, createContentValues(marker, marker.trackId()));
|
||||
Uri uri = contentResolver.insert(CustomContentProvider.UrlType.MARKERS.getUri(), createContentValues(marker, marker.trackId()));
|
||||
return new Marker.Id(ContentUris.parseId(uri));
|
||||
}
|
||||
|
||||
@@ -491,7 +492,7 @@ public class ContentProviderUtils {
|
||||
if (maxCount >= 0) {
|
||||
sortOrder += " LIMIT " + maxCount;
|
||||
}
|
||||
return contentResolver.query(MarkerColumns.CONTENT_URI, projection, selection, selectionArgs, sortOrder);
|
||||
return contentResolver.query(CustomContentProvider.UrlType.MARKERS.getUri(), projection, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
public MarkerIterator searchMarkers(Track.Id trackId, String query) {
|
||||
@@ -509,7 +510,7 @@ public class ContentProviderUtils {
|
||||
MarkerColumns.DESCRIPTION + " LIKE ? OR " +
|
||||
MarkerColumns.TYPE_LOCALIZED + " LIKE ?";
|
||||
selectionArgs = new String[]{"%" + query + "%", "%" + query + "%", "%" + query + "%"};
|
||||
sortOrder = MarkerColumns.DEFAULT_SORT_ORDER + " DESC";
|
||||
sortOrder = BaseColumns._ID + " DESC";
|
||||
}
|
||||
|
||||
return new MarkerIterator(this, getMarkerCursor(null, selection, selectionArgs, sortOrder, -1));
|
||||
@@ -567,7 +568,7 @@ public class ContentProviderUtils {
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
values[i] = createContentValues(trackPoints.get(i), trackId);
|
||||
}
|
||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI_BY_ID, values);
|
||||
return contentResolver.bulkInsert(CustomContentProvider.UrlType.TRACKPOINTS_BY_ID.getUri(), values);
|
||||
}
|
||||
|
||||
//TODO Set trackId in this method.
|
||||
@@ -576,7 +577,7 @@ public class ContentProviderUtils {
|
||||
for (int i = 0; i < markers.size(); i++) {
|
||||
values[i] = createContentValues(markers.get(i), trackId);
|
||||
}
|
||||
return contentResolver.bulkInsert(MarkerColumns.CONTENT_URI, values);
|
||||
return contentResolver.bulkInsert(CustomContentProvider.UrlType.MARKERS.getUri(), values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -597,7 +598,7 @@ public class ContentProviderUtils {
|
||||
selectionArgs = new String[]{Long.toString(trackId.id())};
|
||||
}
|
||||
|
||||
return getTrackPointCursor(null, selection, selectionArgs, TrackPointsColumns.DEFAULT_SORT_ORDER);
|
||||
return getTrackPointCursor(null, selection, selectionArgs, BaseColumns._ID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -608,7 +609,7 @@ public class ContentProviderUtils {
|
||||
* @return the content provider URI of the inserted trackPoint
|
||||
*/
|
||||
public Uri insertTrackPoint(TrackPoint trackPoint, Track.Id trackId) {
|
||||
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(trackPoint, trackId));
|
||||
return contentResolver.insert(CustomContentProvider.UrlType.TRACKPOINTS_BY_ID.getUri(), createContentValues(trackPoint, trackId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -683,7 +684,7 @@ public class ContentProviderUtils {
|
||||
* @param sortOrder the sort order
|
||||
*/
|
||||
private Cursor getTrackPointCursor(String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
return contentResolver.query(TrackPointsColumns.CONTENT_URI_BY_ID, projection, selection, selectionArgs, sortOrder);
|
||||
return contentResolver.query(CustomContentProvider.UrlType.TRACKPOINTS_BY_ID.getUri(), projection, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
public static String formatIdListForUri(Track.Id... trackIds) {
|
||||
@@ -718,7 +719,7 @@ public class ContentProviderUtils {
|
||||
|
||||
public SensorStatistics getSensorStats(@NonNull Track.Id trackId) {
|
||||
SensorStatistics sensorStatistics = null;
|
||||
try (Cursor cursor = contentResolver.query(ContentUris.withAppendedId(TracksColumns.CONTENT_URI_SENSOR_STATS, trackId.id()), null, null, null, null)) {
|
||||
try (Cursor cursor = contentResolver.query(ContentUris.withAppendedId(CustomContentProvider.UrlType.TRACKS_SENSOR_STATS.getUri(), trackId.id()), null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
final int MAX_HR_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_MAX_HR);
|
||||
final int AVG_HR_INDEX = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALIAS_AVG_HR);
|
||||
@@ -743,7 +744,7 @@ public class ContentProviderUtils {
|
||||
@NonNull
|
||||
public List<AggregatedStatistic> getAggregatedStatisticsForTracks(TrackSelection selection) {
|
||||
SelectionData data = selection.buildSelection();
|
||||
try (Cursor cursor = contentResolver.query(TracksColumns.CONTENT_URI_AGGREGATED_STATISTICS, null, data.selection(), data.getSelectionArgs(), null)) {
|
||||
try (Cursor cursor = contentResolver.query(CustomContentProvider.UrlType.TRACKS_AGGREGATED_STATISTICS.getUri(), null, data.selection(), data.getSelectionArgs(), null)) {
|
||||
ArrayList<AggregatedStatistic> aggregatedStatistics = new ArrayList<>(cursor.getCount());
|
||||
while (cursor.moveToNext()) {
|
||||
aggregatedStatistics.add(new AggregatedStatistic(
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -73,51 +74,39 @@ public class CustomContentProvider extends ContentProvider {
|
||||
*/
|
||||
private final String SENSOR_STATS_QUERY =
|
||||
"WITH time_select AS " +
|
||||
"(SELECT t1." + TrackPointsColumns.TIME + " * (t1." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")) time_value " +
|
||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t1 " +
|
||||
"WHERE t1." + TrackPointsColumns._ID + " > t." + TrackPointsColumns._ID + " AND t1." + TrackPointsColumns.TRACKID + " = ? ORDER BY _id LIMIT 1) " +
|
||||
"(SELECT t1." + TrackPointsColumns.TIME + " * (t1." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")) time_value " +
|
||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t1 " +
|
||||
"WHERE t1." + TrackPointsColumns._ID + " > t." + TrackPointsColumns._ID + " AND t1." + TrackPointsColumns.TRACKID + " = ? ORDER BY _id LIMIT 1) " +
|
||||
|
||||
"SELECT " +
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_HEARTRATE + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_HR + ", " +
|
||||
"SELECT " +
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_HEARTRATE + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_HR + ", " +
|
||||
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_HEARTRATE + ") " + TrackPointsColumns.ALIAS_MAX_HR + ", " +
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_HEARTRATE + ") " + TrackPointsColumns.ALIAS_MAX_HR + ", " +
|
||||
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_CADENCE + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_CADENCE + ", " +
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_CADENCE + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_CADENCE + ", " +
|
||||
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_CADENCE + ") " + TrackPointsColumns.ALIAS_MAX_CADENCE + ", " +
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_CADENCE + ") " + TrackPointsColumns.ALIAS_MAX_CADENCE + ", " +
|
||||
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_POWER + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_POWER + ", " +
|
||||
"SUM(t." + TrackPointsColumns.SENSOR_POWER + " * (COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ")) " +
|
||||
"/ " +
|
||||
"SUM(COALESCE(MAX(t." + TrackPointsColumns.TIME + ", (SELECT time_value FROM time_select)), t." + TrackPointsColumns.TIME + ") - t." + TrackPointsColumns.TIME + ") " + TrackPointsColumns.ALIAS_AVG_POWER + ", " +
|
||||
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_POWER + ") " + TrackPointsColumns.ALIAS_MAX_POWER + " " +
|
||||
"MAX(t." + TrackPointsColumns.SENSOR_POWER + ") " + TrackPointsColumns.ALIAS_MAX_POWER + " " +
|
||||
|
||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t " +
|
||||
"WHERE t." + TrackPointsColumns.TRACKID + " = ? " +
|
||||
"AND t." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")";
|
||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t " +
|
||||
"WHERE t." + TrackPointsColumns.TRACKID + " = ? " +
|
||||
"AND t." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")";
|
||||
|
||||
public CustomContentProvider() {
|
||||
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath(), UrlType.TRACKPOINTS.ordinal());
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_ID.getPath() + "/#", UrlType.TRACKPOINTS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, TrackPointsColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.TRACKPOINTS_BY_TRACKID.ordinal());
|
||||
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath(), UrlType.TRACKS.ordinal());
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI_SENSOR_STATS.getPath() + "/#", UrlType.TRACKS_SENSOR_STATS.ordinal());
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI_AGGREGATED_STATISTICS.getPath(), UrlType.TRACKS_AGGREGATED_STATISTICS.ordinal());
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath() + "/*", UrlType.TRACKS_BY_ID.ordinal());
|
||||
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath() + "/#", UrlType.MARKERS_BY_ID.ordinal());
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI_BY_TRACKID.getPath() + "/*", UrlType.MARKERS_BY_TRACKID.ordinal());
|
||||
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, UrlType.DASHBOARD_TRACKS_BY_IDS.getPath() + "*", UrlType.DASHBOARD_TRACKS_BY_IDS.ordinal());
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.getPath() + "*", UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.ordinal());
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.getPath() + "*", UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.ordinal());
|
||||
UrlType[] urlTypes = UrlType.values();
|
||||
for (int i = 0; i < urlTypes.length; i++) {
|
||||
uriMatcher.addURI(AUTHORITY_PACKAGE, urlTypes[i].getPathWithFilter(), i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -180,23 +169,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri url) {
|
||||
String contentType = getUrlType(url).contentType;
|
||||
if (contentType != null) {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
//TODO replace with getUrlType(url).getContentType()
|
||||
return switch (getUrlType(url)) {
|
||||
case TRACKPOINTS -> TrackPointsColumns.CONTENT_TYPE;
|
||||
case TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
|
||||
case TRACKS -> TracksColumns.CONTENT_TYPE;
|
||||
case MARKERS -> MarkerColumns.CONTENT_TYPE;
|
||||
case MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
|
||||
case DASHBOARD_TRACKS_BY_IDS -> UrlType.DASHBOARD_TRACKS_BY_IDS.contentType;
|
||||
case DASHBOARD_TRACKPOINTS_BY_TRACKIDS -> UrlType.DASHBOARD_TRACKPOINTS_BY_TRACKIDS.contentType;
|
||||
case DASHBOARD_MARKERS_BY_TRACKIDS -> UrlType.DASHBOARD_MARKERS_BY_TRACKIDS.contentType;
|
||||
default -> throw new IllegalArgumentException("Unknown URL " + url);
|
||||
};
|
||||
return getUrlType(url).contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -251,7 +224,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS -> {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER;
|
||||
sortOrder = sort != null ? sort : BaseColumns._ID;
|
||||
}
|
||||
case TRACKPOINTS_BY_ID -> {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
@@ -272,13 +245,13 @@ public class CustomContentProvider extends ContentProvider {
|
||||
} else {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
}
|
||||
sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER;
|
||||
sortOrder = sort != null ? sort : BaseColumns._ID;
|
||||
}
|
||||
case TRACKS_BY_ID -> {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
}
|
||||
case DASHBOARD_TRACKS_BY_IDS -> {
|
||||
case DASHBOARD_TRACKS_BY_IDS -> {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
queryBuilder.setProjectionMap(DataProvider.DATA_PROJECTIONMAP_TRACKS);
|
||||
@@ -303,7 +276,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
}
|
||||
case MARKERS -> {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
|
||||
sortOrder = sort != null ? sort : BaseColumns._ID;
|
||||
}
|
||||
case MARKERS_BY_ID -> {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
@@ -414,7 +387,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
}
|
||||
long rowId = db.insert(TrackPointsColumns.TABLE_NAME, TrackPointsColumns._ID, values);
|
||||
if (rowId >= 0) {
|
||||
return ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_ID.buildUpon(), rowId).build();
|
||||
return ContentUris.appendId(UrlType.TRACKPOINTS_BY_ID.getUri().buildUpon(), rowId).build();
|
||||
}
|
||||
throw new SQLiteException("Failed to insert a track point " + url);
|
||||
}
|
||||
@@ -422,7 +395,7 @@ public class CustomContentProvider extends ContentProvider {
|
||||
private Uri insertTrack(Uri url, ContentValues contentValues) {
|
||||
long rowId = db.insert(TracksColumns.TABLE_NAME, TracksColumns._ID, contentValues);
|
||||
if (rowId >= 0) {
|
||||
return ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), rowId).build();
|
||||
return ContentUris.appendId(UrlType.TRACKS.getUri().buildUpon(), rowId).build();
|
||||
}
|
||||
throw new SQLException("Failed to insert a track " + url);
|
||||
}
|
||||
@@ -430,53 +403,49 @@ public class CustomContentProvider extends ContentProvider {
|
||||
private Uri insertMarker(Uri url, ContentValues contentValues) {
|
||||
long rowId = db.insert(MarkerColumns.TABLE_NAME, MarkerColumns._ID, contentValues);
|
||||
if (rowId >= 0) {
|
||||
return ContentUris.appendId(MarkerColumns.CONTENT_URI.buildUpon(), rowId).build();
|
||||
return ContentUris.appendId(UrlType.MARKERS.getUri().buildUpon(), rowId).build();
|
||||
}
|
||||
throw new SQLException("Failed to insert a marker " + url);
|
||||
}
|
||||
|
||||
//TODO Add ContentType as attribute
|
||||
public enum UrlType {
|
||||
TRACKPOINTS,
|
||||
TRACKPOINTS_BY_ID,
|
||||
TRACKPOINTS("/trackpoints", "", "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint"),
|
||||
TRACKPOINTS_BY_ID("/trackpoints", "#", "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint"),
|
||||
//TODO Check if still used
|
||||
TRACKPOINTS_BY_TRACKID,
|
||||
TRACKS,
|
||||
TRACKS_BY_ID,
|
||||
TRACKS_SENSOR_STATS,
|
||||
TRACKS_AGGREGATED_STATISTICS,
|
||||
MARKERS,
|
||||
MARKERS_BY_ID,
|
||||
TRACKPOINTS_BY_TRACKID("/trackpoints/trackid", "*", "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint"),
|
||||
TRACKS("/tracks", "", "vnd.android.cursor.dir/vnd.de.dennisguse.track"),
|
||||
TRACKS_BY_ID("/tracks", "#", "vnd.android.cursor.dir/vnd.de.dennisguse.track"),
|
||||
TRACKS_SENSOR_STATS("/tracks/sensorstats", "#", "vnd.android.cursor.dir/vnd.de.dennisguse.sensorstats"),
|
||||
TRACKS_AGGREGATED_STATISTICS("/tracks/aggregated/", "", "vnd.android.cursor.dir/vnd.de.dennisguse.aggregatedstatistics"),
|
||||
MARKERS("/markers", "", "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint"),
|
||||
MARKERS_BY_ID("/markers", "#", "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint"),
|
||||
@Deprecated //TODO Should not be used anymore
|
||||
MARKERS_BY_TRACKID,
|
||||
MARKERS_BY_TRACKID("/markers/trackid", "*", "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint"),
|
||||
|
||||
DASHBOARD_TRACKS_BY_IDS(Uri.parse(CONTENT_BASE_URI + "/dashboard/tracks/"), TracksColumns.CONTENT_TYPE),
|
||||
DASHBOARD_TRACKPOINTS_BY_TRACKIDS(Uri.parse(CONTENT_BASE_URI + "/dashboard/trackpoints/"), TrackPointsColumns.CONTENT_TYPE),
|
||||
DASHBOARD_MARKERS_BY_TRACKIDS(Uri.parse(CONTENT_BASE_URI + "/dashboard/markers/"), MarkerColumns.CONTENT_TYPE);
|
||||
DASHBOARD_TRACKS_BY_IDS("/dashboard/tracks", "*", "vnd.android.cursor.dir/vnd.de.dennisguse.track"),
|
||||
DASHBOARD_TRACKPOINTS_BY_TRACKIDS("/dashboard/trackpoints", "*", "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint"),
|
||||
DASHBOARD_MARKERS_BY_TRACKIDS("/dashboard/markers", "*", "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint");
|
||||
|
||||
private final Uri uri;
|
||||
private final String path;
|
||||
|
||||
private final String filter;
|
||||
|
||||
private final String contentType;
|
||||
|
||||
//TODO Add Uri as attribute:
|
||||
//TODO Add ContentType as attribute:
|
||||
@Deprecated()
|
||||
UrlType() {
|
||||
this.uri = null;
|
||||
this.contentType = null;
|
||||
}
|
||||
|
||||
UrlType(Uri uri, String contentType) {
|
||||
this.uri = uri;
|
||||
UrlType(@NonNull String path, String filter, @NonNull String contentType) {
|
||||
this.path = path;
|
||||
this.filter = filter;
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return uri;
|
||||
return Uri.parse(CONTENT_BASE_URI + path);
|
||||
}
|
||||
|
||||
String getPath() {
|
||||
return uri.getPath();
|
||||
private String getPathWithFilter() {
|
||||
if (TextUtils.isEmpty(filter))
|
||||
return path;
|
||||
return path + "/" + filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
}
|
||||
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
|
||||
|
||||
Uri uri = Uri.parse(TracksColumns.CONTENT_URI + "/" + trackFileFormat.getPreferenceId() + "/" + trackIdBuilder + "/" + Uri.encode(filename));
|
||||
Uri uri = Uri.parse(UrlType.TRACKS.getUri() + "/" + trackFileFormat.getPreferenceId() + "/" + trackIdBuilder + "/" + Uri.encode(filename));
|
||||
|
||||
Log.d(TAG, "Created uri " + uri + " with MIME " + trackFileFormat.getMimeType());
|
||||
|
||||
@@ -192,7 +192,7 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
String[] trackIdsString = trackIds.stream().map(id -> String.valueOf(id.id())).toArray(String[]::new);
|
||||
String whereClause = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
|
||||
|
||||
try (Cursor cursor = super.query(TracksColumns.CONTENT_URI, null, whereClause, trackIdsString, TracksColumns._ID)) {
|
||||
try (Cursor cursor = super.query(UrlType.TRACKS.getUri(), null, whereClause, trackIdsString, TracksColumns._ID)) {
|
||||
while (cursor.moveToNext()) {
|
||||
tracks.add(ContentProviderUtils.createTrack(cursor));
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ 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.data.statistics.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.data.tables.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.services.RecordingStatus;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.handlers.AltitudeCorrectionManager;
|
||||
@@ -120,7 +118,7 @@ public class TrackDataHub {
|
||||
notifyTracksTableUpdate(listeners);
|
||||
}
|
||||
};
|
||||
contentResolver.registerContentObserver(TracksColumns.CONTENT_URI, false, tracksTableObserver);
|
||||
contentResolver.registerContentObserver(CustomContentProvider.UrlType.TRACKS.getUri(), false, tracksTableObserver);
|
||||
|
||||
trackPointsTableObserver = new ContentObserver(handler) {
|
||||
@Override
|
||||
@@ -128,7 +126,7 @@ public class TrackDataHub {
|
||||
notifyTrackPointsTableUpdate(true, listeners);
|
||||
}
|
||||
};
|
||||
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
|
||||
contentResolver.registerContentObserver(CustomContentProvider.UrlType.TRACKPOINTS_BY_ID.getUri(), false, trackPointsTableObserver);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
@@ -14,13 +15,13 @@ public enum ActivityType {
|
||||
|
||||
AIRPLANE("airplane", R.string.activity_type_airplane, R.drawable.ic_activity_flight_24dp, true),
|
||||
ATV("ATV", R.string.activity_type_atv, R.drawable.ic_activity_drive_24dp, true),
|
||||
BIKING("biking", R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
BIKING("biking", List.of("bike", "indoor cycling"), R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
BLIMP("blimp", R.string.activity_type_blimp, ActivityIcon.ICON_UNKNOWN, true),
|
||||
BOAT("boat", R.string.activity_type_boat, R.drawable.ic_activity_boat_24dp, true),
|
||||
CLIMBING("climbing", R.string.activity_type_climbing, R.drawable.ic_activity_climbing_24dp, false),
|
||||
COMMERCIAL_AIRPLANE("commercial airplane", R.string.activity_type_commercial_airplane, R.drawable.ic_activity_flight_24dp, true),
|
||||
CROSS_COUNTRY_SKIING("cross-country skiing", R.string.activity_type_cross_country_skiing, R.drawable.ic_activity_skiing_24dp, true),
|
||||
CYCLING("cycling", R.string.activity_type_cycling, R.drawable.ic_activity_bike_24dp, true),
|
||||
CYCLING("cycling", List.of("cycle"), R.string.activity_type_cycling, R.drawable.ic_activity_bike_24dp, true),
|
||||
DIRT_BIKE("dirt bike", R.string.activity_type_dirt_bike, R.drawable.ic_activity_mtb_24dp, true),
|
||||
DONKEY_BACK_RIDING("donkey back riding", R.string.activity_type_donkey_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
DRIVING("driving", R.string.activity_type_driving, R.drawable.ic_activity_drive_24dp, true),
|
||||
@@ -30,21 +31,21 @@ public enum ActivityType {
|
||||
FERRY("ferry", R.string.activity_type_ferry, R.drawable.ic_activity_boat_24dp, true),
|
||||
FRISBEE("frisbee", R.string.activity_type_frisbee, ActivityIcon.ICON_UNKNOWN, true),
|
||||
GLIDING("gliding", R.string.activity_type_gliding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
GRAVEL_BIKING("gravel biking", R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
GRAVEL_BIKING("gravel biking", List.of("gravel bike"), R.string.activity_type_gravel_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
HANG_GLIDING("hang gliding", R.string.activity_type_hang_gliding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
HELICOPTER("helicopter", R.string.activity_type_helicopter, ActivityIcon.ICON_UNKNOWN, true),
|
||||
HIKING("hiking", R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
HIKING("hiking", List.of("hike"), R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
HORSE_BACK_RIDING("horse back riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
HOT_AIR_BALLOON("hot air balloon", R.string.activity_type_hot_air_balloon, ActivityIcon.ICON_UNKNOWN, true),
|
||||
ICE_SAILING("ice sailing", R.string.activity_type_ice_sailing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
INLINE_SKATING("inline skating", R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp, true),
|
||||
INLINE_SKATING("inline skating", List.of("inline skate"), R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp, true),
|
||||
KAYAKING("kayaking", R.string.activity_type_kayaking, R.drawable.ic_activity_kayaking_24dp, true),
|
||||
KITE_SURFING("kite surfing", R.string.activity_type_kite_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
LAND_SAILING("land sailing", R.string.activity_type_land_sailing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
MIXED_TYPE("mixed type", R.string.activity_type_mixed_type, ActivityIcon.ICON_UNKNOWN, true),
|
||||
MOTOR_BIKE("motor bike", R.string.activity_type_motor_bike, R.drawable.ic_activity_motorbike_24dp, true),
|
||||
MOTOR_BOATING("motor boating", R.string.activity_type_motor_boating, R.drawable.ic_activity_boat_24dp, true),
|
||||
MOUNTAIN_BIKING("mountain biking", R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true),
|
||||
MOUNTAIN_BIKING("mountain biking", List.of("mountain bike"), R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true),
|
||||
OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_off_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
OTHER("other", R.string.activity_type_other, ActivityIcon.ICON_UNKNOWN, true),
|
||||
PADDLING("paddling", R.string.activity_type_paddling, ActivityIcon.ICON_UNKNOWN, true),
|
||||
@@ -53,46 +54,57 @@ public enum ActivityType {
|
||||
RC_BOAT("RC boat", R.string.activity_type_rc_boat, R.drawable.ic_activity_boat_24dp, true),
|
||||
RC_HELICOPTER("RC helicopter", R.string.activity_type_rc_helicopter, ActivityIcon.ICON_UNKNOWN, true),
|
||||
RIDING("riding", R.string.activity_type_horse_back_riding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
ROAD_BIKING("road biking", R.string.activity_type_road_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
ROAD_BIKING("road biking", List.of("road bike"), R.string.activity_type_road_biking, R.drawable.ic_activity_bike_24dp, true),
|
||||
ROLLER_SKIING("roller skiing", R.string.activity_type_roller_skiing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
ROWING("rowing", R.string.activity_type_rowing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
RUNNING("running", R.string.activity_type_running, R.drawable.ic_activity_run_24dp, false),
|
||||
RUNNING("running", List.of("run", "treadmill"), R.string.activity_type_running, R.drawable.ic_activity_run_24dp, false),
|
||||
SAILING("sailing", R.string.activity_type_sailing, R.drawable.ic_activity_sailing_24dp, true),
|
||||
KICKSCOOTER("kickscooter", R.string.activity_type_kickscooter, R.drawable.ic_activity_scooter_24dp, true),
|
||||
SEAPLANE("seaplane", R.string.activity_type_seaplane, R.drawable.ic_activity_flight_24dp, true),
|
||||
SKATE_BOARDING("skateboarding", R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp, true),
|
||||
SKATE_BOARDING("skateboarding", List.of("skateboard"), R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp, true),
|
||||
SKATING("skating", R.string.activity_type_skating, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SKIING("skiing", R.string.activity_type_skiing, R.drawable.ic_activity_skiing_24dp, true),
|
||||
SKY_JUMPING("sky jumping", R.string.activity_type_sky_jumping, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SLED("sled", R.string.activity_type_sled, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SNOW_BOARDING("snowboarding", R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp, true),
|
||||
SNOW_BOARDING("snowboarding", List.of("snowboard"), R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp, true),
|
||||
SNOW_SHOEING("snow shoeing", R.string.activity_type_snow_shoeing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SPEED_WALKING("speed walking", R.string.activity_type_speed_walking, R.drawable.ic_activity_walk_24dp, false),
|
||||
STREET_RUNNING("street running", R.string.activity_type_street_running, R.drawable.ic_activity_run_24dp, false),
|
||||
SURFING("surfing", R.string.activity_type_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
TRACK_CYCLING("track cycling", R.string.activity_type_track_cycling, R.drawable.ic_activity_bike_24dp, true),
|
||||
TRACK_RUNNING("track running", R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||
TRACK_RUNNING("track running", List.of("track run"), R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||
TRAIL_HIKING("trail hiking", R.string.activity_type_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
TRAIL_RUNNING("trail running", R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||
TRAIL_RUNNING("trail running", List.of("trail run"), R.string.activity_type_trail_running, R.drawable.ic_activity_run_24dp, false),
|
||||
TRAIN("train", R.string.activity_type_train, ActivityIcon.ICON_UNKNOWN, true),
|
||||
ULTIMATE_FRISBEE("ultimate frisbee", R.string.activity_type_ultimate_frisbee, ActivityIcon.ICON_UNKNOWN, true),
|
||||
WAKEBOARDING("wakeboarding", R.string.activity_type_wakeboarding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
WALKING("walking", R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp, false),
|
||||
WALKING("walking", List.of("walk"), R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp, false),
|
||||
WATER_SKIING("water skiing", R.string.activity_type_water_skiing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
WIND_SURFING("wind surfing", R.string.activity_type_wind_surfing, ActivityIcon.ICON_UNKNOWN, true),
|
||||
SWIMMING("swimming", R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp, false),
|
||||
SWIMMING_OPEN("swimming in open water", R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp, false),
|
||||
SWIMMING("swimming", List.of("swim"), R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp, false),
|
||||
SWIMMING_OPEN("swimming in open water", List.of("swimming openwater", "open water swimming"), R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp, false),
|
||||
WORKOUT("workout", R.string.activity_type_workout, R.drawable.ic_activity_workout_24dp, false),
|
||||
UNKNOWN("unknown", R.string.activity_type_unknown, ActivityIcon.ICON_UNKNOWN, true);
|
||||
|
||||
final String id;
|
||||
|
||||
/**
|
||||
* Spellings used by other applications that {@link #normalize(String)} cannot map onto {@link #id}.
|
||||
* Already normalized.
|
||||
*/
|
||||
final List<String> synonyms;
|
||||
|
||||
final int iconDrawableId;
|
||||
final boolean showSpeedPreferred;
|
||||
final int localizedStringId;
|
||||
|
||||
ActivityType(String id, int localizedStringId, int iconDrawableId, boolean showSpeedPreferred) {
|
||||
this(id, List.of(), localizedStringId, iconDrawableId, showSpeedPreferred);
|
||||
}
|
||||
|
||||
ActivityType(String id, List<String> synonyms, int localizedStringId, int iconDrawableId, boolean showSpeedPreferred) {
|
||||
this.id = id;
|
||||
this.synonyms = synonyms;
|
||||
this.localizedStringId = localizedStringId;
|
||||
this.iconDrawableId = iconDrawableId;
|
||||
this.showSpeedPreferred = showSpeedPreferred;
|
||||
@@ -121,12 +133,39 @@ public enum ActivityType {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lowercases and reduces "_", "-" and whitespace runs to a single space.
|
||||
* Lowercasing is locale-independent: under a Turkish locale the I of "RUNNING" would otherwise
|
||||
* lowercase to a dotless i and stop matching "running".
|
||||
*/
|
||||
@NonNull
|
||||
private static String normalize(String activityTypeId) {
|
||||
if (activityTypeId == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return activityTypeId.toLowerCase(Locale.ROOT)
|
||||
.replaceAll("[\\s_-]+", " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an activity type as written by another application; GPX's type element is free text.
|
||||
*/
|
||||
@NonNull
|
||||
public static ActivityType findBy(String activityTypeId) {
|
||||
return Arrays.stream(ActivityType.values()).filter(
|
||||
it -> it.id.equals(activityTypeId)
|
||||
).findFirst()
|
||||
.orElse(ActivityType.UNKNOWN);
|
||||
String normalized = normalize(activityTypeId);
|
||||
if (normalized.isEmpty()) {
|
||||
return ActivityType.UNKNOWN;
|
||||
}
|
||||
|
||||
return Arrays.stream(ActivityType.values())
|
||||
.filter(it -> normalize(it.id).equals(normalized))
|
||||
.findFirst()
|
||||
.orElseGet(() -> Arrays.stream(ActivityType.values())
|
||||
.filter(it -> it.synonyms.contains(normalized))
|
||||
.findFirst()
|
||||
.orElse(ActivityType.UNKNOWN));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -50,15 +50,6 @@ public class ExtremityMonitor {
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum value.
|
||||
*
|
||||
* @param min the minimum value
|
||||
*/
|
||||
public void setMin(double min) {
|
||||
this.min = min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum value seen.
|
||||
*/
|
||||
@@ -66,15 +57,6 @@ public class ExtremityMonitor {
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum value.
|
||||
*
|
||||
* @param max the maximum value
|
||||
*/
|
||||
public void setMax(double max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the min and the max with a new value.
|
||||
*
|
||||
|
||||
@@ -151,12 +151,14 @@ public class SegmentStatisticUpdater {
|
||||
}
|
||||
|
||||
public void addMovingTime(TrackPoint trackPoint, TrackPoint lastTrackPoint) {
|
||||
Duration movingDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
|
||||
addMovingTime(Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()));
|
||||
}
|
||||
|
||||
if (movingDuration.isNegative()) {
|
||||
throw new RuntimeException("Moving time cannot be negative: " + lastTrackPoint.getTime() + " is after " + trackPoint.getTime());
|
||||
public void addMovingTime(Duration duration) {
|
||||
if (duration.isNegative()) {
|
||||
throw new RuntimeException("Moving time cannot be negative");
|
||||
}
|
||||
this.movingDuration = this.movingDuration.plus(movingDuration);
|
||||
movingDuration = movingDuration.plus(duration);
|
||||
}
|
||||
|
||||
private Speed getAverageMovingSpeed() {
|
||||
|
||||
+21
-11
@@ -143,7 +143,6 @@ public class TrackStatisticsUpdater {
|
||||
}
|
||||
|
||||
{
|
||||
// Update total distance
|
||||
Distance movingDistance = null;
|
||||
if (trackPoint.sensorDistance() != null) {
|
||||
movingDistance = trackPoint.sensorDistance();
|
||||
@@ -153,16 +152,7 @@ public class TrackStatisticsUpdater {
|
||||
// GPS-based distance/speed
|
||||
movingDistance = trackPoint.distanceToPrevious(lastTrackPoint);
|
||||
}
|
||||
if (movingDistance != null) {
|
||||
currentSegment.addTotalDistance(movingDistance);
|
||||
}
|
||||
|
||||
if (!isIdle
|
||||
&& !trackPoint.isSegmentManualStart()
|
||||
&& lastTrackPoint != null) {
|
||||
currentSegment.addMovingTime(trackPoint, lastTrackPoint);
|
||||
}
|
||||
|
||||
boolean isIdleTransition = false;
|
||||
if (trackPoint.isIdleTriggered()) {
|
||||
isIdle = true;
|
||||
} else if (isIdle) {
|
||||
@@ -170,6 +160,26 @@ public class TrackStatisticsUpdater {
|
||||
if (movingDistance != null
|
||||
&& movingDistance.greaterOrEqualThan(PreferencesUtils.getRecordingDistanceInterval())) {
|
||||
isIdle = false;
|
||||
isIdleTransition = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (movingDistance != null && !isIdle) {
|
||||
currentSegment.addTotalDistance(movingDistance);
|
||||
}
|
||||
|
||||
if (!isIdle
|
||||
&& !trackPoint.isSegmentManualStart()
|
||||
&& lastTrackPoint != null) {
|
||||
if (isIdleTransition) {
|
||||
if (trackPoint.position().hasSpeed() && trackPoint.position().speed().toMPS() > 0) {
|
||||
double timeSeconds = movingDistance.toM() / trackPoint.position().speed().toMPS();
|
||||
currentSegment.addMovingTime(Duration.ofMillis((long)(timeSeconds * 1000)));
|
||||
} else {
|
||||
currentSegment.addMovingTime(Duration.ofMillis((long)(movingDistance.toM() * 1000)));
|
||||
}
|
||||
} else {
|
||||
currentSegment.addMovingTime(trackPoint, lastTrackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,10 @@
|
||||
package de.dennisguse.opentracks.data.tables;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||
|
||||
/**
|
||||
* Constants for markers table.
|
||||
@@ -32,11 +30,6 @@ import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||
public interface MarkerColumns extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "markers";
|
||||
Uri CONTENT_URI = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||
Uri CONTENT_URI_BY_TRACKID = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint";
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
|
||||
String DEFAULT_SORT_ORDER = _ID;
|
||||
|
||||
// Columns
|
||||
String NAME = "name";
|
||||
|
||||
@@ -16,11 +16,8 @@
|
||||
|
||||
package de.dennisguse.opentracks.data.tables;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||
|
||||
/**
|
||||
* Constants for the track points table.
|
||||
*
|
||||
@@ -29,11 +26,6 @@ import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||
public interface TrackPointsColumns extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "trackpoints";
|
||||
Uri CONTENT_URI_BY_ID = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||
Uri CONTENT_URI_BY_TRACKID = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint";
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint";
|
||||
String DEFAULT_SORT_ORDER = _ID;
|
||||
|
||||
// Columns
|
||||
String TRACKID = "trackid";
|
||||
|
||||
@@ -16,11 +16,8 @@
|
||||
|
||||
package de.dennisguse.opentracks.data.tables;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||
|
||||
/**
|
||||
* Constants for the tracks table.
|
||||
*
|
||||
@@ -29,12 +26,6 @@ import de.dennisguse.opentracks.data.CustomContentProvider;
|
||||
public interface TracksColumns extends BaseColumns {
|
||||
|
||||
String TABLE_NAME = "tracks";
|
||||
Uri CONTENT_URI = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||
Uri CONTENT_URI_SENSOR_STATS = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/" + TABLE_NAME + "/sensorstats");
|
||||
Uri CONTENT_URI_AGGREGATED_STATISTICS = Uri.parse(CustomContentProvider.CONTENT_BASE_URI + "/" + TABLE_NAME + "/aggregated/");
|
||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
|
||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
|
||||
String DEFAULT_SORT_ORDER = _ID;
|
||||
|
||||
// Columns
|
||||
String UUID = "uuid"; // identifier to make tracks globally unique (prevent re-import)
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment implements
|
||||
ActivityType.WORKOUT
|
||||
);
|
||||
|
||||
private static int getPosition(Context context, ActivityType activityType) {
|
||||
private static int getPosition(ActivityType activityType) {
|
||||
if (activityType == null) {
|
||||
return -1;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment implements
|
||||
}
|
||||
|
||||
final ChooseActivityTypeImageAdapter imageAdapter = new ChooseActivityTypeImageAdapter(iconDrawableIds);
|
||||
int position = getPosition(getContext(), preselectedActivityType);
|
||||
int position = getPosition(preselectedActivityType);
|
||||
if (position != -1) {
|
||||
imageAdapter.setSelected(position);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public class ExportActivity extends AbstractActivity {
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
// List of tracks to be exported.
|
||||
private ArrayList<ExportTask> exportTasks;
|
||||
private final ArrayList<ExportTask> exportTasks = new ArrayList<>();
|
||||
|
||||
private final LinkedBlockingQueue<PendingConflict> conflictsQueue = new LinkedBlockingQueue<>();
|
||||
private final Handler conflictsHandler = new Handler();
|
||||
@@ -183,7 +183,7 @@ public class ExportActivity extends AbstractActivity {
|
||||
trackExportTotalCount = savedInstanceState.getInt(BUNDLE_TOTAL_COUNT);
|
||||
directoryFiles = savedInstanceState.getStringArrayList(BUNDLE_DIRECTORY_FILES);
|
||||
trackErrors = savedInstanceState.getStringArrayList(BUNDLE_TRACK_ERRORS);
|
||||
exportTasks = new ArrayList<>(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS));
|
||||
exportTasks.addAll(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS));
|
||||
setProgress();
|
||||
nextExport(null);
|
||||
}
|
||||
@@ -226,7 +226,6 @@ public class ExportActivity extends AbstractActivity {
|
||||
return false;
|
||||
}
|
||||
|
||||
exportTasks = new ArrayList<>();
|
||||
if (allInOneFile) {
|
||||
String filename = "OpenTracks-Backup";
|
||||
exportTasks.add(new ExportTask(filename, trackFileFormat, tracks.stream().map(Track::id).toList()));
|
||||
|
||||
@@ -36,7 +36,7 @@ public class ExportUtils {
|
||||
private static final String TAG = ExportUtils.class.getSimpleName();
|
||||
|
||||
public static void postWorkoutExport(AppCompatActivity context, Track.Id trackId) {
|
||||
if (PreferencesUtils.shouldInstantExportAfterWorkout()) {
|
||||
if (PreferencesUtils.shouldInstantExportToFile()) {
|
||||
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat();
|
||||
DocumentFile directory = IntentUtils.toDocumentFile(context, PreferencesUtils.getDefaultExportDirectoryUri());
|
||||
|
||||
|
||||
@@ -282,10 +282,11 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
Statistics trackStatistics = track.statistics();
|
||||
printWriter.println("<gpxtrkx:TrackStatsExtension>");
|
||||
printWriter.println("<gpxtrkx:Distance>" + trackStatistics.totalDistance().toM() + "</gpxtrkx:Distance>");
|
||||
printWriter.println("<gpxtrkx:TimerTime>" + trackStatistics.totalDuration().getSeconds() + "</gpxtrkx:TimerTime>");
|
||||
printWriter.println("<gpxtrkx:TotalElapsedTime>" + trackStatistics.totalDuration().getSeconds() + "</gpxtrkx:TotalElapsedTime>");
|
||||
printWriter.println("<gpxtrkx:MovingTime>" + trackStatistics.movingDuration().getSeconds() + "</gpxtrkx:MovingTime>");
|
||||
printWriter.println("<gpxtrkx:StoppedTime>" + trackStatistics.getStoppedTime().getSeconds() + "</gpxtrkx:StoppedTime>");
|
||||
printWriter.println("<gpxtrkx:MaxSpeed>" + trackStatistics.maxSpeed().toMPS() + "</gpxtrkx:MaxSpeed>");
|
||||
printWriter.println("<gpxtrkx:MovingSpeed>" + trackStatistics.getAverageMovingSpeed().toMPS() + "</gpxtrkx:MovingSpeed>");
|
||||
if (trackStatistics.altitudeGainLoss() != null) {
|
||||
printWriter.println("<gpxtrkx:Ascent>" + trackStatistics.altitudeGainLoss().gain_m() + "</gpxtrkx:Ascent>");
|
||||
printWriter.println("<gpxtrkx:Descent>" + trackStatistics.altitudeGainLoss().loss_m() + "</gpxtrkx:Descent>");
|
||||
@@ -384,10 +385,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
|
||||
printWriter.println("</trkpt>");
|
||||
|
||||
if (cumulativeDistance != null) {
|
||||
return cumulativeDistance;
|
||||
}
|
||||
return Distance.ZERO;
|
||||
return Objects.requireNonNullElse(cumulativeDistance, Distance.ZERO);
|
||||
}
|
||||
|
||||
private Double cumulateSensorData(TrackPoint trackPoint, List<TrackPoint> sensorPoints, Function<TrackPoint, Double> map) {
|
||||
|
||||
@@ -18,8 +18,11 @@ package de.dennisguse.opentracks.io.file.importer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
@@ -71,28 +74,44 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
private static final String TAG_TRACK_POINT = "trkpt";
|
||||
private static final String TAG_TRACK_SEGMENT = "trkseg";
|
||||
private static final String TAG_TYPE = "type";
|
||||
private static final String TAG_TYPE_LOCALIZED = "opentracks:typeTranslated";
|
||||
private static final String TAG_MARKER = "wpt";
|
||||
private static final String TAG_ID = "opentracks:trackid";
|
||||
|
||||
private static final String ATTRIBUTE_LAT = "lat";
|
||||
private static final String ATTRIBUTE_LON = "lon";
|
||||
|
||||
private static final String TAG_EXTENSION_SPEED = "gpxtpx:speed";
|
||||
/**
|
||||
* Often speed is exported without the proper namespace.
|
||||
*/
|
||||
private static final String TAG_EXTENSION_SPEED_COMPAT = "speed";
|
||||
private static final String TAG_EXTENSION_HEARTRATE = "gpxtpx:hr";
|
||||
private static final String TAG_EXTENSION_TEMPERATURE = "gpxtpx:atemp";
|
||||
private static final String TAG_EXTENSION_CADENCE = "gpxtpx:cad";
|
||||
private static final String TAG_EXTENSION_POWER = "pwr:PowerInWatts";
|
||||
|
||||
private static final String TAG_EXTENSION_GAIN = "opentracks:gain";
|
||||
private static final String TAG_EXTENSION_LOSS = "opentracks:loss";
|
||||
private static final String TAG_EXTENSION_DISTANCE = "opentracks:distance";
|
||||
private static final String TAG_EXTENSION_ACCURACY_HORIZONTAL = "opentracks:accuracy_horizontal";
|
||||
private static final String TAG_EXTENSION_ACCURACY_VERTICAL = "opentracks:accuracy_vertical";
|
||||
/**
|
||||
* Garmin Trackpoint Extensions.
|
||||
*/
|
||||
private static final String NS_GARMIN_TRACKPOINT_EXTENSION_V1 = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1";
|
||||
private static final String NS_GARMIN_TRACKPOINT_EXTENSION_V2 = "http://www.garmin.com/xmlschemas/TrackPointExtension/v2";
|
||||
private static final String TAG_GARMIN_TPX_EXTENSION_SPEED = "speed";
|
||||
private static final String TAG_GARMIN_TPX_EXTENSION_HEARTRATE = "hr";
|
||||
private static final String TAG_GARMIN_TPX_EXTENSION_TEMPERATURE = "atemp";
|
||||
private static final String TAG_GARMIN_TPX_EXTENSION_CADENCE = "cad";
|
||||
|
||||
/**
|
||||
* Garmin Trackpoint Extensions.
|
||||
*/
|
||||
private static final String NS_GARMIN_POWER_EXTENSION_V1 = "http://www.garmin.com/xmlschemas/PowerExtension/v1";
|
||||
private static final String TAG_GARMIN_PWR_EXTENSION_POWER = "PowerInWatts";
|
||||
|
||||
/**
|
||||
* OpenTracks Extensions.
|
||||
*/
|
||||
private static final String NS_OPENTRACKS = "http://opentracksapp.com/xmlschemas/v1";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_TRACK_ID = "trackid";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_TYPE_LOCALIZED = "typeTranslated";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_GAIN = "gain";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_LOSS = "loss";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_DISTANCE = "distance";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_ACCURACY_HORIZONTAL = "accuracy_horizontal";
|
||||
private static final String TAG_OPENTRACKS_EXTENSION_ACCURACY_VERTICAL = "accuracy_vertical";
|
||||
|
||||
private Locator locator;
|
||||
|
||||
private final Context context;
|
||||
@@ -142,8 +161,8 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes) {
|
||||
switch (tag) {
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
switch (qName) {
|
||||
case TAG_MARKER:
|
||||
onMarkerStart(attributes);
|
||||
break;
|
||||
@@ -165,105 +184,128 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) {
|
||||
switch (tag) {
|
||||
case TAG_GPX -> onFileEnd();
|
||||
case TAG_MARKER -> onMarkerEnd();
|
||||
case TAG_TRACK -> {
|
||||
if (activityTypeLocalized == null) {
|
||||
// Backward compatibility: up v4.9.1 as <type> contained localized content.
|
||||
activityTypeLocalized = activityType;
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
if (NS_GARMIN_TRACKPOINT_EXTENSION_V1.equals(uri) || NS_GARMIN_TRACKPOINT_EXTENSION_V2.equals(uri)) {
|
||||
switch (localName) {
|
||||
case TAG_GARMIN_TPX_EXTENSION_SPEED -> {
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
}
|
||||
}
|
||||
trackImporter.setTrackData(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset);
|
||||
zoneOffset = null;
|
||||
}
|
||||
case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
|
||||
case TAG_TRACK_POINT -> currentSegment.add(createTrackPoint());
|
||||
case TAG_NAME -> {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
case TAG_GARMIN_TPX_EXTENSION_HEARTRATE -> {
|
||||
if (content != null) {
|
||||
heartrate = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_DESCRIPTION -> {
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
case TAG_GARMIN_TPX_EXTENSION_TEMPERATURE -> {
|
||||
if (content != null) {
|
||||
temperature = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_TYPE -> { //Track or Marker/WPT
|
||||
if (content != null) {
|
||||
// In older version this might be localized content.
|
||||
activityType = content.trim();
|
||||
markerType = content.trim();
|
||||
case TAG_GARMIN_TPX_EXTENSION_CADENCE -> {
|
||||
if (content != null) {
|
||||
cadence = content.trim();
|
||||
}
|
||||
}
|
||||
default -> Log.w(TAG, "Unknown Garmin trackpoint extension tag " + localName);
|
||||
}
|
||||
case TAG_TYPE_LOCALIZED -> {
|
||||
if (content != null) {
|
||||
activityTypeLocalized = content.trim();
|
||||
} else if (NS_GARMIN_POWER_EXTENSION_V1.equals(uri)) {
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
switch (localName) {
|
||||
case TAG_GARMIN_PWR_EXTENSION_POWER -> {
|
||||
if (content != null) {
|
||||
power = content.trim();
|
||||
}
|
||||
}
|
||||
default -> Log.w(TAG, "Unknown Garmin power extension tag " + localName);
|
||||
}
|
||||
case TAG_TIME -> {
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
} else if (NS_OPENTRACKS.equals(uri)) {
|
||||
switch (localName) {
|
||||
case TAG_OPENTRACKS_EXTENSION_TRACK_ID -> {
|
||||
if (content != null) {
|
||||
uuid = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_ALTITUDE -> {
|
||||
if (content != null) {
|
||||
altitude = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_TYPE_LOCALIZED -> {
|
||||
if (content != null) {
|
||||
activityTypeLocalized = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_SPEED, TAG_EXTENSION_SPEED_COMPAT -> {
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_GAIN -> {
|
||||
if (content != null) {
|
||||
gain = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_HEARTRATE -> {
|
||||
if (content != null) {
|
||||
heartrate = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_LOSS -> {
|
||||
if (content != null) {
|
||||
loss = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_TEMPERATURE -> {
|
||||
if (content != null) {
|
||||
temperature = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_DISTANCE -> {
|
||||
if (content != null) {
|
||||
sensorDistance = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_CADENCE -> {
|
||||
if (content != null) {
|
||||
cadence = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_ACCURACY_HORIZONTAL -> {
|
||||
if (content != null) {
|
||||
accuracyHorizontal = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_POWER -> {
|
||||
if (content != null) {
|
||||
power = content.trim();
|
||||
case TAG_OPENTRACKS_EXTENSION_ACCURACY_VERTICAL -> {
|
||||
if (content != null) {
|
||||
accuracyVertical = content.trim();
|
||||
}
|
||||
}
|
||||
default -> Log.w(TAG, "Unknown OpenTracks extension tag " + localName);
|
||||
}
|
||||
case TAG_ID -> {
|
||||
if (content != null) {
|
||||
uuid = content.trim();
|
||||
} else {
|
||||
switch (qName) {
|
||||
case TAG_GPX -> onFileEnd();
|
||||
case TAG_MARKER -> onMarkerEnd();
|
||||
case TAG_TRACK -> {
|
||||
if (activityTypeLocalized == null) {
|
||||
// Backward compatibility: up v4.9.1 as <type> contained localized content.
|
||||
activityTypeLocalized = activityType;
|
||||
}
|
||||
trackImporter.setTrackData(context, name, uuid, description, activityTypeLocalized, activityType, zoneOffset);
|
||||
zoneOffset = null;
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_GAIN -> {
|
||||
if (content != null) {
|
||||
gain = content.trim();
|
||||
case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
|
||||
case TAG_TRACK_POINT -> {
|
||||
TrackPoint trackPoint = createTrackPoint();
|
||||
if (trackPoint != null) currentSegment.add(trackPoint);
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_LOSS -> {
|
||||
if (content != null) {
|
||||
loss = content.trim();
|
||||
case TAG_NAME -> {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_DISTANCE -> {
|
||||
if (content != null) {
|
||||
sensorDistance = content.trim();
|
||||
case TAG_DESCRIPTION -> {
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_ACCURACY_HORIZONTAL -> {
|
||||
if (content != null) {
|
||||
accuracyHorizontal = content.trim();
|
||||
case TAG_TYPE -> { //Track or Marker/WPT
|
||||
if (content != null) {
|
||||
// In older version this might be localized content.
|
||||
activityType = content.trim();
|
||||
markerType = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_ACCURACY_VERTICAL -> {
|
||||
if (content != null) {
|
||||
accuracyVertical = content.trim();
|
||||
case TAG_TIME -> {
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
}
|
||||
}
|
||||
case TAG_ALTITUDE -> {
|
||||
if (content != null) {
|
||||
altitude = content.trim();
|
||||
}
|
||||
}
|
||||
case TAG_EXTENSION_SPEED_COMPAT -> {
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,8 +326,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
currentSegment.clear();
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private TrackPoint createTrackPoint() throws ParsingException {
|
||||
if (TextUtils.isEmpty(time)) return null;
|
||||
|
||||
OffsetDateTime parsedTime;
|
||||
try {
|
||||
parsedTime = StringUtils.parseTime(time);
|
||||
@@ -468,7 +512,10 @@ public class GPXTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
|
||||
TrackPoint trackPoint = createTrackPoint();
|
||||
|
||||
if (trackPoint == null) {
|
||||
Log.w(TAG, "Marker with no TrackPoint");
|
||||
return;
|
||||
}
|
||||
if (!trackPoint.position().hasLocation()) {
|
||||
Log.w(TAG, "Marker with invalid coordinates ignored: " + trackPoint.position());
|
||||
return;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user